I am looking to find a way of checking if a point exists inside a polygon in Google Maps v3 (JavaScript). I've searched everywhere and the only solutions I have found so far have been to do with getting the bounds of the polygon, but the code shown seems to just create a rectangle and keeps expanding its surface area to include all relevant points.
By the way, the reason I can't just use a big square i.e. getting a polygons bounds, is that I have bordering polygons on the map and they can not expand into each other's territory.
EDIT
Following on from the reply below, I have tried implementing the example code using one of my existing polygons but it is just saying that it is not defined and I can't figure out why.
Here is my declaration:
myCoordinates = [
new google.maps.LatLng(0.457301,-0.597382),
new google.maps.LatLng(0.475153,-0.569916),
new google.maps.LatLng(0.494379,-0.563049),
new google.maps.LatLng(0.506738,-0.553436),
new google.maps.LatLng(0.520470,-0.541077),
new google.maps.LatLng(0.531456,-0.536957),
new google.maps.LatLng(0.556174,-0.552063),
new google.maps.LatLng(0.536949,-0.596008),
new google.maps.LatLng(0.503991,-0.612488),
new google.maps.LatLng(0.473780,-0.612488) ];
polyOptions = {
path: myCoordinates,
strokeColor: "#FF0000",
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: "#0000FF",
fillOpacity: 0.6 };
var rightShoulderFront = new google.maps.Polygon(polyOptions);
rightShoulderFront.setMap(map);
and here is where I am checking for the point:
var coordinate = selectedmarker.getPosition();
var isWithinPolygon = rightShoulderFront.containsLatLng(coordinate);
console.log(isWithinPolygon);
But it keeps coming up with the error: Uncaught ReferenceError: rightShoulderFront is not defined
One algorithm to solve this is ray-casting. See an explanation here.
And you can find code implementing this for the Google Maps JS API V3 here.
HTH.
You can do this quite simply with Google maps geometry library.
First be sure to add the google maps geometry library.
<script type="text/javascript" src="//maps.googleapis.com/maps/api/js?libraries=geometry&sensor=false"></script>
Then, define your polygon
var rightShoulderFront = new google.maps.Polygon({
paths: myCoordinates
});
rightShoulderFront .setMap(map);
I'm going to add an event listener to handle a 'click' event, but you can adapt to fit your needs
google.maps.event.addListener(rightShoulderFront , 'click', isWithinPoly);
Create a function to handle our click event an check if coordinate exists within polygon using Google's geometry library
/** #this {google.maps.Polygon} */
function isWithinPoly(event){
var isWithinPolygon = google.maps.geometry.poly.containsLocation(event.latLng, this);
console.log(isWithinPolygon);
}
You have a very good example of containsLocation() method in Google Maps API documentation.
You should have a look about the Gmaps.js library. It has a quite simple method about geofence.
var coordinate = new google.maps.LatLng(0.457301,-0.597382);//replace with your lat and lng values
var isWithinPolygon = google.maps.geometry.poly.containsLocation(coordinate, yourPolygon);
Dont forget to include the library in your googleapis script. Read more...
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=geometry"></script>
The example and implementation do not take into account that a polygon can cross the 180 degrees boundary.
The implementation does take it into account (implicitly) in the bounding box check, but the polygon check fails.
I have used the same thing and working fine and its offline code I have written this code in PHP you can write it any programming language.
class pointLocation {
var $pointOnVertex = true; // Check if the point sits exactly on one of the vertices?
function pointLocation() {
}
function pointInPolygon($point, $polygon, $pointOnVertex = true) {
$this->pointOnVertex = $pointOnVertex;
// Transform string coordinates into arrays with x and y values
$point = $this->pointStringToCoordinates($point);
$vertices = array();
foreach ($polygon as $vertex) {
$vertices[] = $this->pointStringToCoordinates($vertex);
}
// Check if the point sits exactly on a vertex
if ($this->pointOnVertex == true and $this->pointOnVertex($point, $vertices) == true) {
return "vertex";
}
// Check if the point is inside the polygon or on the boundary
$intersections = 0;
$vertices_count = count($vertices);
for ($i=1; $i < $vertices_count; $i++) {
$vertex1 = $vertices[$i-1];
$vertex2 = $vertices[$i];
if ($vertex1['y'] == $vertex2['y'] and $vertex1['y'] == $point['y'] and $point['x'] > min($vertex1['x'], $vertex2['x']) and $point['x'] < max($vertex1['x'], $vertex2['x'])) { // Check if point is on an horizontal polygon boundary
return "boundary";
}
if ($point['y'] > min($vertex1['y'], $vertex2['y']) and $point['y'] <= max($vertex1['y'], $vertex2['y']) and $point['x'] <= max($vertex1['x'], $vertex2['x']) and $vertex1['y'] != $vertex2['y']) {
$xinters = ($point['y'] - $vertex1['y']) * ($vertex2['x'] - $vertex1['x']) / ($vertex2['y'] - $vertex1['y']) + $vertex1['x'];
if ($xinters == $point['x']) { // Check if point is on the polygon boundary (other than horizontal)
return "boundary";
}
if ($vertex1['x'] == $vertex2['x'] || $point['x'] <= $xinters) {
$intersections++;
}
}
}
// If the number of edges we passed through is odd, then it's in the polygon.
if ($intersections % 2 != 0) {
return "inside";
} else {
return "outside";
}
}
function pointOnVertex($point, $vertices) {
foreach($vertices as $vertex) {
if ($point == $vertex) {
return true;
}
}
}
function pointStringToCoordinates($pointString) {
$coordinates = explode(" ", $pointString);
return array("x" => $coordinates[0], "y" => $coordinates[1]);
}
}
$pointLocation = new pointLocation();
$points = array("22.732965336387213 75.8609390258789");
$polygon = array("22.73549852921309 75.85424423217773","22.72346544538196 75.85561752319336","22.72346544538196 75.87175369262695","22.732332030848273 75.87295532226562","22.740406456758326 75.8686637878418","22.74198962160603 75.85407257080078");
echo '<pre>';
print_r($polygon);
// The last point's coordinates must be the same as the first one's, to "close the loop"
foreach($points as $key => $point) {
echo "point " . ($key+1) . " ($point): " . $pointLocation->pointInPolygon($point, $polygon) . "<br>";
}
?>
I think google has solved it with this method of containsLocation()
https://developers.google.com/maps/documentation/javascript/examples/poly-containsLocation
Related
I am currently working with a KML-file via this plugin: https://github.com/sushihangover/SushiHangover.Android.Maps.Utils
The KML-file i am using gets added succesfully via this codesnippet:
var kmlLayer = new KmlLayer(googleMap, Resource.Raw.campus, Android.App.Application.Context);
kmlLayer.AddLayerToMap();
MoveCameraToKml(kmlLayer);
When it's added I run my MoveCameraToKmlfunction where I try to get the lat, lng of every point but I get a crash on this row foreach (LatLng latLng in ((KmlLineString)geo).GeometryObject); with the errormessage: object reference not set to an instance of an object
void MoveCameraToKml(KmlLayer kmlLayer)
{
//Retrieve the first container in the KML layer
var container = (KmlContainer)kmlLayer.Containers.Iterator().Next();
//Retrieve a nested container within the first container
container = (KmlContainer)container.Containers.Iterator().Next();
//Retrieve the first placemark in the nested container
var placemark = (KmlPlacemark)container.Placemarks.Iterator().Next();
var geo = placemark.Geometry;
if (geo is KmlLineString)
{
foreach (LatLng latLng in ((KmlLineString)geo).GeometryObject) //object reference not set to an instance of an object
{
System.Diagnostics.Debug.WriteLine(latLng);
}
}
}
Any idea why this is giving me a crash? I am following the example of the nuget I downloaded above.
The idea is to store the lat, lngs in a list and use a PolylineOptions to create routes.
That Google sample (MoveCameraToKml) assumes that you are using their Campus KML example. Since the KML you use will be unique to your app, you will need to review your KML/XML elements and write your code to suit your usage.
Here is an example using their Grand Canyon KML LineString hiking path:
https://developers.google.com/kml/documentation/kml_tut#paths
So looking at the KML, we will need to:
Get the first Container
Get the first Placemark in that container
Check if it has geometry and is a KML LineString
Obtain the LatLng array via GeometryJavaObject()
Use those Latlngs to build a camera viewport and move to it.
"Drive" the camera along those individual points
Grand Canyon KmlLineString Example:
void MoveCameraToKml(KmlLayer kmlLayer)
{
var container = (KmlContainer)kmlLayer.Containers.Iterator().Next();
var placemark = (KmlPlacemark)container.Placemarks.Iterator().Next();
if (placemark.HasGeometry && placemark.Geometry is KmlLineString)
{
var lineString = placemark.Geometry as KmlLineString;
var latlngArray = lineString.GeometryJavaObject() as Java.Util.ArrayList;
using (var builder = new LatLngBounds.Builder())
{
foreach (LatLng latLng in latlngArray.ToEnumerable())
{
builder.Include(latLng);
}
googleMap.MoveCamera(CameraUpdateFactory.NewLatLngBounds(
builder.Build(), mapFragment.View.Width, mapFragment.View.Height, 1)
);
}
Task.Run(async () => // run camera along KmlLineString
{
foreach (LatLng latLng in latlngArray.ToEnumerable())
{
await Task.Delay(2000);
RunOnUiThread(() => googleMap.MoveCamera(CameraUpdateFactory.NewLatLng(latLng)));
}
});
}
}
Geometry can be KmlPoint, KmlLineString, KmlPolygon or a KmlMultiGeometry.
var geo = placemark.Geometry;
if (geo is KmlPolygon) then {
var poly = (KmlPolygon)geo;
}
I am trying to use the containsLocation from the Google geometry library, but can't get it to work...
var point = new google.maps.LatLng(51.331, 3.538);
var poly = [
new google.maps.LatLng(51.401818509550615, 3.547626782103622),
new google.maps.LatLng(51.397574277049365, 3.563607598960424),
new google.maps.LatLng(51.398540111384975, 3.567880788749134),
... // it is a lot bigger
];
if(google.maps.geometry.poly.containsLocation(point, poly) == true) {
alert("yes");
}
The Javascript console gives an error, but that points to a function in Google's lib. So I assume the problem should lie somewhere in this function.
Oke, stupid me
I was wrong using all the coords as an array, I had to use the created polygon object.
var polyOptions = {
...
}
draw = new google.maps.Polygon(polyOptions);
draw.setMap(map);
if(google.maps.geometry.poly.containsLocation(point, draw) == true) {
alert("yes");
}
i had the same problem ([object Object]), I could solve that creating the polygon variable like that:
draw = new google.maps.Polygon({paths:polyOptions});
Then the problem disappeared.
If you zoom a google map out the world will start to repeat horizontally. Using .getBounds() seems to return the longitude at the edges of the displayed map image. But I would like to get minimum and maximum longitudes for the current view of the real world.
For example in this image .getBounds() says that the longitude ranges between 116 and 37 degrees (giving a width of -79 degrees!). The range I'm looking for is -244 to +37.
(or even -180 to +37 as this is the extremes of the world that is viewable around the map centre.)
And another example. Here I'm looking for -180 to +180 ...
You can try it for yourself here...
http://jsfiddle.net/spiderplant0/EBNYT/
(Apologies if this has been answered before - I did find some old similar questions but none seemed to have satisfactory answers).
I ran into the same issues today, but I think I finally figured it out.
In the first scenario above, you can use map.getBounds().toSpan() to get the width in longitude.....as long as the map did not wrap around.
For the second scenario where the map wraps around, I extended the google.maps.OverlayView() to get the google.maps.MapCanvasProjection object. From there you can call the function getWorldWidth().
It will give you the world width in pixel, then you can compare it with your map container's width. If your container is bigger, your map has wrapped around.
Don't know if the function is meant for this but it works.
The answer proposed by user1292293 worked for me (Google map api V3)
Extension of google.maps.OverlayView()
function MyMapOverlay() {}
MyMapOverlay.prototype = new google.maps.OverlayView();
MyMapOverlay.prototype.onAdd = function() {};
MyMapOverlay.prototype.draw = function() {};
MyMapOverlay.prototype.onRemove = function() {};
Add overlay to the map
var map = new google.maps.Map(domContainer, {...});
var overlay = new MyMapOverlay();
overlay.setMap(map);
check if map wraps around:
var proj = overlay.getProjection();
var wwidth = 0;
if (proj) wwidth = proj.getWorldWidth();
var mapsWrapsAround=false;
if (__wwidth > 0 && __wwidth < domContainer.width()) mapsWrapsAround = true;
I used the answer from rebpp to prevent the map from wrapping by setting the getWorldWidth. Here's the MapWrappingPrevent I created.
To use this just call
var wrapPreventer = new MapWrappingPrevent(_map);
/* This class prevents wrapping of a map by adjusting the max-width */
function MapWrappingPrevent(map) {
var self = this;
this.setMap(map);
map.addListener('zoom_changed', function () {
self.onZoomChanged();
});
}
MapWrappingPrevent.prototype = new google.maps.OverlayView();
MapWrappingPrevent.prototype.onAdd = function () { this.onZoomChanged(); };
MapWrappingPrevent.prototype.draw = function () { };
MapWrappingPrevent.prototype.onRemove = function () { };
MapWrappingPrevent.prototype.onZoomChanged = function () {
var proj = this.getProjection();
if (proj) {
var wrappingWidth = proj.getWorldWidth();
$(this.getMap().getDiv()).css({'max-width': wrappingWidth + 'px'})
}
};
I cannot seem to figure out why the object returned by getProjection() is undefined. Here is my code:
// Handles the completion of the rectangle
var ne = recBounds.getNorthEast();
var sw = recBounds.getSouthWest();
$("#map_tools_selat").attr( 'value', sw.lat() );
$("#map_tools_nwlat").attr( 'value', ne.lat() );
$("#map_tools_selng").attr( 'value', ne.lng() );
$("#map_tools_nwlng").attr( 'value', sw.lng() );
// Set Zoom Level
$("#map_tools_zoomlevel").attr( 'value', HAR.map.getZoom()+1 );
document.getElementById("map_tools_centerLat").value = HAR.map.getCenter().lat();
document.getElementById("map_tools_centerLong").value = HAR.map.getCenter().lng();
// All this junk below is for getting pixel coordinates for a lat/lng =/
MyOverlay.prototype = new google.maps.OverlayView();
MyOverlay.prototype.onAdd = function() { }
MyOverlay.prototype.onRemove = function() { }
MyOverlay.prototype.draw = function() { }
function MyOverlay(map) { this.setMap(map); }
var overlay = new MyOverlay(HAR.map);
var projection = overlay.getProjection();
// END - all the junk
var p = projection.fromLatLngToContainerPixel(recBounds.getCenter());
alert(p.x+", "+p.y);
My error is: Cannot call method 'fromLatLngToContainerPixel' of undefined
Actually, i the reason why this happens is because the projection object is created after the map is idle after panning / zooming. So, a better solution is to listen on the idle event of the google.maps.Map object, and get a reference to the projection there:
// Create your map and overlay
var map;
MyOverlay.prototype = new google.maps.OverlayView();
MyOverlay.prototype.onAdd = function() { }
MyOverlay.prototype.onRemove = function() { }
MyOverlay.prototype.draw = function() { }
function MyOverlay(map) { this.setMap(map); }
var overlay = new MyOverlay(map);
var projection;
// Wait for idle map
google.maps.event.addListener(map, 'idle', function() {
// Get projection
projection = overlay.getProjection();
})
I kind of figured out what was going on. Even though it is still not crystal clear why this happens, I know that I had to instantiate the variable "overlay" right after instantiating my google map (HAR.map). So I practically moved that code snippet into my HAR class and now i use:
HAR.canvassOverlay.getProjection().fromLatLngToContainerPixel( recBounds.getCenter() );
So now, every time I create a map via my class "HAR" I also have a parallel OverlayView object within my class.
The Error could have been with losing scope of my class object, but I think it was more of the map event "projection_changed" not being fired. I got a hint from the map API docs for map class, under method getProjection():
"Returns the current Projection. If the map is not yet initialized (i.e. the mapType is still null) then the result is null. Listen to projection_changed and check its value to ensure it is not null."
If you are getting the similar issue, make sure that you assign your overlayView.setMAP( YOUR_MAP_OBJECT ) closely after instantiating the map object.
I have two maps on a page, one is a map of the world, and the other is a closeup of the current place they picked on the map of the world. I would like to set different zoom min/max levels for each map but:
G_NORMAL_MAP.getMinimumResolution = function(){return 11};
Seems to set the same min/max for both maps, I can't set them to different levels.
I think the problem is probably elsewhere in your code - I'm not sure exactly how you're using that function.
Here is a method that will work. You can re-write it to have less duplication.
map1 = new GMap2(document.getElementById("map1"));
map1.addControl(new GLargeMapControl3D());
map1.addControl(new GMenuMapTypeControl());
var mt = map1.getMapTypes();
// Overwrite the getMinimumResolution() and getMaximumResolution() methods
for (var i=0; i<mt.length; i++) {
mt[i].getMinimumResolution = function() {return 7;}
mt[i].getMaximumResolution = function() {return 11;}
}
map1.setCenter(new GLatLng(40,-100), 8);
map2 = new GMap2(document.getElementById("map2"));
map2.addControl(new GLargeMapControl3D());
map2.addControl(new GMenuMapTypeControl());
var mt = map2.getMapTypes();
// Overwrite the getMinimumResolution() and getMaximumResolution() methods
for (var i=0; i<mt.length; i++) {
mt[i].getMinimumResolution = function() {return 2;}
mt[i].getMaximumResolution = function() {return 6;}
}
map2.setCenter(new GLatLng(40,-100), 4);
Do you need 2 different maps? You can use the Map2.showMapBlowup() function to show a subarea which will be a zoomed in section on the current map.
I'm sorry I don't know if you can actually do it with 2 different maps.
http://code.google.com/apis/maps/documentation/reference.html#GMap2.showMapBlowup
You could use a custom map type and copy the G_NORMAL_MAP members of using a library like Prototype.
var G_MY_MAP = Class.create(G_NORMAL_MAP, {
getMinimumResolution: function()
{
return 11;
}
});
Then on your second map:
secondMap.addMapType(G_MY_MAP);
secondMap.setMapType(G_MY_MAP);
No idea if this will work, just a brain storm.....