I am trying to find out the LatLng based on my x,y pixel coordinates (and of course map options, such as zoom and center).
In order to do so, I posted another question and someone came up with this solution, from this post:
/**
* #param {google.maps.Map} map
* #param {google.maps.Point} point
* #param {int} z
* #return {google.maps.LatLng}
*/
var pointToLatlng = function(map, point, z){
var scale = Math.pow(2, z);
var normalizedPoint = new google.maps.Point(point.x / scale, point.y / scale);
var latlng = map.getProjection().fromPointToLatLng(normalizedPoint);
return latlng;
};
As you can notice from the code sample, the function uses as argument a google.maps.Point, therefore I need to convert my screen pixel coordinate into a google.maps.Point and I have no clue how, since their documentation of the API is not quite verbose...
Can you please help me? Or am I missing something on the way?
After some research and some fails I came up with a solution.
Following the documentation from this link I found out that the google Points are computed in the range of x:[0-256], y:[0-256] (a tile being 256x256 pixels) and the (0,0) point being the leftmost point of the map (check the link for more information).
However, my approach is as it follows:
having the x and y coordinates (which are coordinates on the screen - on the map) I computed the percentage where the x and y coordinates were placed in response to the div containing the map (in my case, the hole window)
computed the NortEast and SouthWest LatLng bounds of the (visible) map
converted the bounds in google Points
computed the new lat and lng, in google points, with the help of the boundaries and percentage of x and y
// retrieve the lat lng for the far extremities of the (visible) map
var latLngBounds = map.getBounds();
var neBound = latLngBounds.getNorthEast();
var swBound = latLngBounds.getSouthWest();
// convert the bounds in pixels
var neBoundInPx = map.getProjection().fromLatLngToPoint(neBound);
var swBoundInPx = map.getProjection().fromLatLngToPoint(swBound);
// compute the percent of x and y coordinates related to the div containing the map; in my case the screen
var procX = x/window.innerWidth;
var procY = y/window.innerHeight;
// compute new coordinates in pixels for lat and lng;
// for lng : subtract from the right edge of the container the left edge,
// multiply it by the percentage where the x coordinate was on the screen
// related to the container in which the map is placed and add back the left boundary
// you should now have the Lng coordinate in pixels
// do the same for lat
var newLngInPx = (neBoundInPx.x - swBoundInPx.x) * procX + swBoundInPx.x;
var newLatInPx = (swBoundInPx.y - neBoundInPx.y) * procY + neBoundInPx.y;
var finalResult = new google.maps.Point(newLngInPx, newLatInPx);
You could use an overlay that draws nothing in order to get at the super valuable function, fromContainerPixelToLatLng()
var overlay = new google.maps.OverlayView();
overlay.draw = function() {}; // empty function required
overlay.setMap(map);
var coordinates = overlay.getProjection().fromContainerPixelToLatLng(
new google.maps.Point(x, y)
);
console.log(coordinates.lat + ", " + coordinates.lng);
Related
I know how to find near by locations from MySQL database using Round circle radius query and I have given answer of the same on another SO question as well here.
But I wish to do some different thing from this now. Here what happened is the query returns the result from center of the point which includes entire circle of radius. I wish to get points only of the half circle. I know this is possible and its all mathematical calculation and I am little weak in it that's why asking for experts help.
See this image, it will give very clear idea.
As you can see in the image only front part location is needed, not the back side part. Need to ignore the back side part. Also I have divided the radius in different color to make them appear as zones - like red is zone1, orange is zone 2 and yellow is zone 3. This are virtual zones to filter the data (locations).
All suggestions are welcome.
You can plot points inside a segment using Haversine/Spherical Law of Cosines for the radius. Then use pointInPolygon() to find only those within segment. You will also require function to create polygon.
polySides = number of sides in polygon
pointLatArr = Lat of point in in polygon array
pointLngArr = Lng of point in in polygon array
dat.lat = Lat from Haversine results
dat.lng = Lng from Haversine results
if (pointInPolygonpolySides,pointLatArr,pointLngArr,dat.lat,dat.lng)){
var latlng = new google.maps.LatLng(dat.lat,dat.lng);
addMarker(latlng,dat.name);
bounds.extend(latlng);
}
function pointInPolygon(polySides,polyX,polyY,x,y) {
var j = polySides-1 ;
oddNodes = 0;
for (i=0; i<polySides; i++) {
if (polyY[i]<y && polyY[j]>=y || polyY[j]<y && polyY[i]>=y) {
if (polyX[i]+(y-polyY[i])/(polyY[j]-polyY[i])*(polyX[j]-polyX[i])<x) {
oddNodes=!oddNodes;
}
}
j=i; }
return oddNodes;
}
Function for segment polygon
function drawSegment(start,end,radius) {
var d2r = Math.PI / 180;
pointLatArr = new Array();
pointLngArr = new Array();
polyLatLngs = new Array(); // latLngs of polygon
var polyLat = (radius /3963.189) / d2r; // miles
var polyLng = polyLat / Math.cos(center.lat() * d2r);
var centerLatLng = new google.maps.LatLng(center.lat(),center.lng());//Center to start
pointLatArr.push(center.lat());
pointLngArr.push(center.lng());
polyLatLngs.push(centerLatLng);
bounds.extend(centerLatLng);
// Create polygon points (extra point to close polygon)
for (var i = start; i < end; i++) {
// Convert degrees to radians
var theta = i * d2r;
var pointLat = center.lat() + (polyLat * Math.sin(theta));
var pointLng = center.lng() + (polyLng * Math.cos(theta));
var pointLatLng = new google.maps.LatLng(
parseFloat(pointLat), parseFloat(pointLng));
polyLatLngs.push(pointLatLng);
pointLatArr.push(pointLat);
pointLngArr.push(pointLng);
bounds.extend(pointLatLng);
}
var centerLatLng = new google.maps.LatLng(center.lat(),center.lng());//End to center
polyLatLngs.push(centerLatLng);
pointLatArr.push(center.lat());
pointLngArr.push(center.lng());
polySides = polyLatLngs.length;
Map using this technique
}
See Demo
I have a dynamic tile set where I do NOT want to allow panning outside of its bounds.
The below code gets me close, but the user can still scroll horizontally outside of strict bounds because it uses the map center for comparison
var strictBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(sw_lat, sw_lon),
new google.maps.LatLng(ne_lat, ne_lon)
);
google.maps.event.addListener(map, 'drag', function()
{
if (strictBounds.contains(map.getCenter())) return;
// We're out of bounds - Move the map back within the bounds
var c = map.getCenter(),
x = c.lng(),
y = c.lat(),
maxX = strictBounds.getNorthEast().lng(),
maxY = strictBounds.getNorthEast().lat(),
minX = strictBounds.getSouthWest().lng(),
minY = strictBounds.getSouthWest().lat();
if (x < minX) x = minX;
if (x > maxX) x = maxX;
if (y < minY) y = minY;
if (y > maxY) y = maxY;
map.setCenter(new google.maps.LatLng(y, x));
});
For quick access, here is the jsfiddle for this solution: http://jsfiddle.net/nYz6k/
Supposed you're happy with the current solution of detecting bounds against the center, all you have to do is to restrict the bounds based on the current map canvas size.
The result that you're getting now is that every corner of the restricting bounds appears on the center of the map when it's full restricting (i.e. both latitude and longitude exceed the bounds corners).
The solution is to actually remove the x and y offset from the bounds, and still be able to check against the center (which is the most efficient solution, compared to other bounds-based checking solutions).
Also, you have to restrict the bounds only when you initialize the map and when the window is resizing, which means that when you pan there is no extra processing overhead besides the check method that you have already provided.
Don't forget to set the maxZoom property for the map (tweak it as needed) because above a determined zoom level the restricting bounds will by themselves fit in the viewport and there's no solution for that.
Important! Use 'center_changed' instead of 'drag' because 'drag' has that sliding behavior that when you finished dragging and set the center, the maps still slides in the direction of the panning.
Before you implement the solution I recommend you to check out how the Maps API coordinate system and projection works, because this solution is heavily based on it, and if you want to tweak it out, it's useful to know this information.
https://developers.google.com/maps/documentation/javascript/maptypes and check out the Custom Map Types -> Map Coordinates section.
Here's what you need to do. First, you need to implement 2 short methods of converting between geographic coordinates (LatLng) and pixel coordinates.
var fromLatLngToPixel = function (latLng) {
var point = map.getProjection().fromLatLngToPoint(latLng);
var zoom = map.getZoom();
return new google.maps.Point(
Math.floor(point.x * Math.pow(2, zoom)),
Math.floor(point.y * Math.pow(2, zoom))
);
}
var fromPixelToLatLng = function (pixel) {
var zoom = map.getZoom();
var point = new google.maps.Point(
pixel.x / Math.pow(2, zoom),
pixel.y / Math.pow(2, zoom)
);
return map.getProjection().fromPointToLatLng(point);
}
Next, implement the method that effectively restricts the bounds. Note that you always have to keep a variable that stores the original bounds, because each time the map canvas is resized, the resulting bounds will change.
For this, let's say originalBounds keeps the bounds you provided with sw_lat, sw_long etc, and shrinkedBounds is modified by this method. You can rename it to strictBounds to still work in your method, but it's up to you. This uses jQuery for getting the width and height of the canvas object.
var shrinkBounds = function () {
zoom = map.getZoom();
// The x and y offset will always be half the current map canvas
// width and height respectively
xoffset = $('#map_canvas').width() / 2;
yoffset = $('#map_canvas').height() / 2;
// Convert the bounds extremities to global pixel coordinates
var pixswOriginal = fromLatLngToPixel(originalBounds.getSouthWest());
var pixneOriginal = fromLatLngToPixel(originalBounds.getNorthEast());
// Shrink the original bounds with the x and y offset
var pixswShrinked = new google.maps.Point(pixswOriginal.x + xoffset, pixswOriginal.y - yoffset);
var pixneShrinked = new google.maps.Point(pixneOriginal.x - xoffset, pixneOriginal.y + yoffset);
// Rebuild the shrinkedBounds object with the modified
shrinkedBounds = new google.maps.LatLngBounds(
fromPixelToLatLng(pixswShrinked),
fromPixelToLatLng(pixneShrinked));
}
Next, all you have to do is to call this method:
once when the map is initialized. Be aware that depending on when you call the method, you may be getting some weird errors because the map might not have yet initialized all it's properties. Best way is to use the projection_changed event.
google.maps.event.addListener(map, 'projection_changed', function (e) {
shrinkBounds();
});
every time the map canvas is resized
google.maps.event.addListener(map, 'resize', function (e) {
shrinkBounds();
});
But the most important part of this is that the 'resize' event is never triggered for programmatic resizes of the map container, so you have to trigger it manually every time you resize the canvas programmatically (if you do, but I doubt you do).
The most common way is to trigger it when the window is resized:
$(window).resize(function () {
google.maps.event.trigger(map, 'resize');
});
After all of this, you can now safely use your method, but as a handler for 'center_changed' and not for 'drag' as I've mentioned earlier.
I set up a jsfiddle with the full working code.
http://jsfiddle.net/nYz6k/
You can see the restricting at the bottom left corner with that little half-marker that shows up, which is positioned in the SW corner of the bounds. There is also one at the NW corner but naturally you can't see it because it's displayed above the position.
Instead of using map.getCenter() as a check, use map.getBounds()
Untested, but a simple way to accomplish this would be to check if the union of both bounds were identical (if not, your map bounds is outside your strict bounds):
var union = strictBounds.union(map.getBounds());
if (strictBounds.equals(union)) { //new map bounds is within strict bounds
Tiborg version above is the best version so far in keeping the bounds...
I noticed a bug that occurs when zoom level and width/height are larger than original bounds
I have modified his code and added a rectangle to see it in action.
var buildBounds = function(sw, ne) {
var swY = sw.lat();
var swX = sw.lng();
var neY = ne.lat();
var neX = ne.lng();
if (swY > neY) {
var cY = (swY + neY) / 2;
swY = cY;
neY = cY;
}
if (swX > neX) {
var cX = (swX + neX) / 2;
swX = cX;
neX = cX;
}
return new google.maps.LatLngBounds(
new google.maps.LatLng(swY, swX),
new google.maps.LatLng(neY, neX));
}
http://jsfiddle.net/p4wgjs6s/
I have made my own map using the google maps api. It is a custom map type based on a really big image.
Is there anyway I can reset the latlng coordinates so they are relative to my image and not the earth's default latlng coordinates?
I.E. Make the top left corner of the image (0,0).
Thanks.
Yes, it's possible with your own custom projection definitions. I modified this lunar surface example with a simple projection that takes the top-left corner to be (0,0) and bottom-right, (50,50). You will most likely need to change the values to work with your app.
I played around a bit with the limits and there looks to still be a hard limit of +/- 90 for latitudes and +/- 180 for longitudes. The longitude will wrap around at 180 degrees. That is, it appears to be impossible to define a custom coordinate of, say, (100, 200).
See the modified lunar surface demo here, click anywhere to display the custom latLng in an alert:
http://jsfiddle.net/yV6xv/79/
function percentProjection() {};
percentProjection.prototype.fromLatLngToPoint = function(latlng) {
// pixel size of tile at zoom 0
var max = 256;
// define bottom-right corner as (50, 50)
var x = max * latlng.lng() / 50;
var y = max * latlng.lat() / 50;
return new google.maps.Point(x, y);
};
percentProjection.prototype.fromPointToLatLng = function(pixel) {
// inverse conversion
var max = 256;
var lng = pixel.x / max * 50;
var lat = pixel.y / max * 50;
return new google.maps.LatLng(lat, lng);
};
I need to know how to retrieve the radius of the viewable zoom level in google maps API v3.
For example, if I am at zoom level 3, and depending on the users screen size (lets just say 400x400 viewable region) how do I get the "radius" circle of the viewable area.
Alternatively I'm currently using map.fitBounds() for all the points I've added to the map so really all I NEED is the radius of all the bounds. What I want is something like "20 miles" that I can feed into my database app.
The radius would equal the distance from the center of the bounds to one of the bound's corners. Using the Great Circle Distance Formula from the calculations from this page, I came up with the following:
var bounds = map.getBounds();
var center = bounds.getCenter();
var ne = bounds.getNorthEast();
// r = radius of the earth in statute miles
var r = 3963.0;
// Convert lat or lng from decimal degrees into radians (divide by 57.2958)
var lat1 = center.lat() / 57.2958;
var lon1 = center.lng() / 57.2958;
var lat2 = ne.lat() / 57.2958;
var lon2 = ne.lng() / 57.2958;
// distance = circle radius from center to Northeast corner of bounds
var dis = r * Math.acos(Math.sin(lat1) * Math.sin(lat2) +
Math.cos(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1));
After playing some more with this, I've noticed that map.getBounds() will contain the full map viewport. But if your LatLngBounds is built by extending to include LatLng points, and then you issue a map.fitBounds(bounds), the api increases the map's viewport a bit so that the bounds "box" has some padding.
If you use the map's current viewport, the radius from the center to the corner of the viewport might be a longer radius than you want. Maybe the distance from the viewport center to the middle of the furthest viewport edge. (If the map isn't a perfect square)
Refactored version of Eric's answer above using google.maps.geometry.spherical namespace (make sure you loaded Geometry library to make this work).
var bounds = map.getBounds();
var center = map.getCenter();
if (bounds && center) {
var ne = bounds.getNorthEast();
// Calculate radius (in meters).
var radius = google.maps.geometry.spherical.computeDistanceBetween(center, ne);
}
I also refactored Eric's answer, mine is as a standalone function, and am returning the result in meters (as needed by the Places API search.
function getBoundsRadius(bounds){
// r = radius of the earth in km
var r = 6378.8
// degrees to radians (divide by 57.2958)
var ne_lat = bounds.getNorthEast().lat() / 57.2958
var ne_lng = bounds.getNorthEast().lng() / 57.2958
var c_lat = bounds.getCenter().lat() / 57.2958
var c_lng = bounds.getCenter().lng() / 57.2958
// distance = circle radius from center to Northeast corner of bounds
var r_km = r * Math.acos(
Math.sin(c_lat) * Math.sin(ne_lat) +
Math.cos(c_lat) * Math.cos(ne_lat) * Math.cos(ne_lng - c_lng)
)
return r_km *1000 // radius in meters
}
I believe Bounds have the getNorthEast() and getSouthWest() methods, but this would give you a rectangle (which bounds really is) and you could than calculate the distance between those two, etc.
To calculate the circle out of that rectandle might be a bit of work...
Maybe this will help: http://code.google.com/apis/maps/articles/mvcfun.html
and the example: http://code.google.com/apis/maps/articles/mvcfun/step6.html
I have a map that shows location points based on the gbounds of the map. For example, any time the map is moved/zoomed, i find the bounds and query for locations that fall within those bounds. Unfortunately I'm unable to display all my locations when fully zoomed out. Reason being, gmaps reports the min/max long as whatever is at the edge of the map, but if you zoom out enough, you can get a longitudinal range that excludes visible locations.
For instance, if you zoom your map so that you see NorthAmerica twice, on the far left and far right. The min/max long are around: -36.5625 to 170.15625. But this almost completely excludes NorthAmerica which lies in the -180 to -60 range. Obviously this is bothersome as you can actually see the continent NorthAmerica (twice), but when I query my for locations in the range from google maps, NorthAmerica isn't returned.
My code for finding the min/max long is:
bounds = gmap.getBounds();
min_lat = bounds.getSouthWest().lat()
max_lat = bounds.getNorthEast().lat()
Has anyone encountered this and can anyone suggest a workaround? Off the top of my head I can only thing of a hack: to check the zoom level and hardcode the min/max lats to -180/180 if necessary, which is definitely unacceptable.
This isn't the most elegant solution, but I tested it and it does work. Check to see if the current bounds contains the bounds of the full map. Google doesn't actually include the north and south pole, so I had to tweak it to go almost all the way north and south.
bounds = gmap.getBounds();
var fullEarth = new GLatLngBounds(new GLatLng(-85, 180), new GLatLng(85, -180)))
var isFull = bounds.containsBounds(fullEarth);
min_lat = isFull ? -90 : bounds.getSouthWest().lat()
max_lat = isFull ? 90 : bounds.getNorthEast().lat()
min_lng = isFull ? 180 : bounds.getSouthWest().lng()
max_lng = isFull ? -180 : bounds.getNorthEast().lng()
Can not even imagine how this HUGE bug is still there !!!
isFullLng and toSpan functions are buggy and you can not rely on them.
My bug workaround for this:
var zoom = map.getZoom();
var bounds = map.getBounds();
var span = bounds.toSpan();
var isFull = span.lng() > 277 || zoom < 2;
Here is my solution, I only need isFullLng() which was apparently removed from API v3:
isFullLng: function() {
var scale = Math.pow(2, map.getZoom()),
bounds = map.getBounds(),
ne = bounds.getNorthEast(),
sw = bounds.getSouthWest(),
lat = (ne.lat() <= 0 && sw.lat() >= 0) || (ne.lat() >= 0 && sw.lat() <= 0) ? 0 : Math.min(Math.abs(ne.lat()), Math.abs(sw.lat())), // closest latitude to equator
deg1 = new google.maps.LatLng(lat, 0),
deg2 = new google.maps.LatLng(lat, 1),
coord1 = map.getProjection().fromLatLngToPoint(deg1),
coord2 = map.getProjection().fromLatLngToPoint(deg2);
// distance for one long degree in pixels for this zoom level
var pixelsPerLonDegree = (coord2.x - coord1.x) * scale;
// width of map's holder should be <= 360 (deg) * pixelsPerLonDegree if full map is displayed
var width = $this.width(); // width of map's holder div
return pixelsPerLonDegree * 360 <= width;
},
isFullLat: function() {
var bounds = map.getBounds(),
ne = bounds.getNorthEast(),
sw = bounds.getSouthWest(),
maxLat = 85; // max lat degree
return ne.lat() >= maxLat && sw.lat() <= -maxLat;
},
So isFull could then be:
isFullLng() && isFullLat()
Note that in isFullLng() the width of map's placeholder is needed, I'm using jQuery and map is rendered in div referenced by $this, so I only call
$this.width()
You should should change that to apply it to your problem.