Multiple circles -> One Polygon? - javascript

Using Google Maps API v3, I was able to create multiple google.maps.Circle objects on my map. However, I now need to "connect" them somehow. I have the following map with multiple circles:
I now need to get it to look something like this:
(source: pcwp.com)
I've looked all over the Internet for solutions, but to no avail. Any ideas?

You may want to consider tackling this problem by adding addional circles at x intervals with increasing radiuses between each point of the path. This would be very easy to implement and will work for any direction of the cyclone. Obviously Matti's suggested solution to create a polygon by connecting all the tangents would be more accurate, but you can consider this as an possible alternative. The main downside is that it may require some effort to make it look pretty, and it will obviously use more client-side resources than if you were to render a single polygon.
Let's start by recreating your map:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<title>Google Maps Cyclones</title>
<script src="http://maps.google.com/maps/api/js?sensor=false"
type="text/javascript"></script>
</head>
<body>
<div id="map" style="width: 600px; height: 400px"></div>
<script type="text/javascript">
var i;
var mapOptions = {
mapTypeId: google.maps.MapTypeId.TERRAIN,
center: new google.maps.LatLng(28.50, -81.50),
zoom: 5
};
var map = new google.maps.Map(document.getElementById("map"),
mapOptions);
var pathPoints = [
new google.maps.LatLng(25.48, -71.26),
new google.maps.LatLng(25.38, -73.70),
new google.maps.LatLng(25.28, -77.00),
new google.maps.LatLng(25.24, -80.11),
new google.maps.LatLng(25.94, -82.71),
new google.maps.LatLng(27.70, -87.14)
];
pathPoints[0].radius = 80;
pathPoints[1].radius = 100;
pathPoints[2].radius = 200;
pathPoints[3].radius = 300;
pathPoints[4].radius = 350;
pathPoints[5].radius = 550;
new google.maps.Polyline({
path: pathPoints,
strokeColor: '#00FF00',
strokeOpacity: 1.0,
strokeWeight: 3,
map: map
});
for (i = 0; i < pathPoints.length; i++) {
new google.maps.Circle({
center: pathPoints[i],
radius: pathPoints[i].radius * 1000,
fillColor: '#FF0000',
fillOpacity: 0.2,
strokeOpacity: 0.5,
strokeWeight: 1,
map: map
});
}
</script>
</body>
</html>
Google Maps Cyclones - Figure 1 http://img186.imageshack.us/img186/1197/mp1h.png
I assume that you heave already arrived to this point, and therefore the above example should be self-explanatory. Basically we have just defined 6 points, along with 6 radiuses, and we have rendered the circles on the map, together with the green path.
Before we continue, we need to define a few methods to be able to calculate the distance and the bearing from one point to another. We will also need a method that will return the destination point when given a bearing and the distance travelled from a source point. Fortunately, there is a very good JavaScript implementation for these methods by Chris Veness at Calculate distance, bearing and more between Latitude/Longitude points. The following methods have been adapted to work with Google's google.maps.LatLng:
Number.prototype.toRad = function() {
return this * Math.PI / 180;
}
Number.prototype.toDeg = function() {
return this * 180 / Math.PI;
}
google.maps.LatLng.prototype.destinationPoint = function(brng, dist) {
dist = dist / 6371;
brng = brng.toRad();
var lat1 = this.lat().toRad(), lon1 = this.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));
if (isNaN(lat2) || isNaN(lon2)) return null;
return new google.maps.LatLng(lat2.toDeg(), lon2.toDeg());
}
google.maps.LatLng.prototype.bearingTo = function(point) {
var lat1 = this.lat().toRad(), lat2 = point.lat().toRad();
var dLon = (point.lng()-this.lng()).toRad();
var y = Math.sin(dLon) * Math.cos(lat2);
var x = Math.cos(lat1)*Math.sin(lat2) -
Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
var brng = Math.atan2(y, x);
return ((brng.toDeg()+360) % 360);
}
google.maps.LatLng.prototype.distanceTo = function(point) {
var lat1 = this.lat().toRad(), lon1 = this.lng().toRad();
var lat2 = point.lat().toRad(), lon2 = point.lng().toRad();
var dLat = lat2 - lat1;
var dLon = lon2 - lon1;
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1) * Math.cos(lat2) *
Math.sin(dLon/2) * Math.sin(dLon/2);
return 6371 * (2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)));
}
We would then need to add another loop that renders the intermediate circles inside the for loop that we used previously to render the original circles. Here is how it can be implemented:
var distanceStep = 50; // Render an intermediate circle every 50km.
for (i = 0; i < pathPoints.length; i++) {
new google.maps.Circle({
center: pathPoints[i],
radius: pathPoints[i].radius * 1000,
fillColor: '#FF0000',
fillOpacity: 0.2,
strokeOpacity: 0.5,
strokeWeight: 1,
map: map
});
if (i < (pathPoints.length - 1)) {
distanceToNextPoint = pathPoints[i].distanceTo(pathPoints[i + 1]);
bearingToNextPoint = pathPoints[i].bearingTo(pathPoints[i + 1]);
radius = pathPoints[i].radius;
radiusIncrement = (pathPoints[i + 1].radius - radius) /
(distanceToNextPoint / distanceStep);
for (j = distanceStep;
j < distanceToNextPoint;
j += distanceStep, radius += radiusIncrement) {
new google.maps.Circle({
center: pathPoints[i].destinationPoint(bearingToNextPoint, j),
radius: radius * 1000,
fillColor: '#FF0000',
fillOpacity: 0.2,
strokeWeight: 0,
map: map
});
}
}
}
This is what we would get:
And this is how it would look without the black stroke around the original circles:
Google Maps Cyclones - Figure 3 http://img181.imageshack.us/img181/2908/mp3t.png
As you may notice, the main challenge will be to render the circles with a consistent opacity, even when they overlap on each other. There are a few options to achieve this, but that could be the topic of another question.
In any case, the following is the full implementation for this example:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<title>Google Maps Cyclones</title>
<script src="http://maps.google.com/maps/api/js?sensor=false"
type="text/javascript"></script>
</head>
<body>
<div id="map" style="width: 600px; height: 400px"></div>
<script type="text/javascript">
Number.prototype.toRad = function() {
return this * Math.PI / 180;
}
Number.prototype.toDeg = function() {
return this * 180 / Math.PI;
}
google.maps.LatLng.prototype.destinationPoint = function(brng, dist) {
dist = dist / 6371;
brng = brng.toRad();
var lat1 = this.lat().toRad(), lon1 = this.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));
if (isNaN(lat2) || isNaN(lon2)) return null;
return new google.maps.LatLng(lat2.toDeg(), lon2.toDeg());
}
google.maps.LatLng.prototype.bearingTo = function(point) {
var lat1 = this.lat().toRad(), lat2 = point.lat().toRad();
var dLon = (point.lng()-this.lng()).toRad();
var y = Math.sin(dLon) * Math.cos(lat2);
var x = Math.cos(lat1)*Math.sin(lat2) -
Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
var brng = Math.atan2(y, x);
return ((brng.toDeg()+360) % 360);
}
google.maps.LatLng.prototype.distanceTo = function(point) {
var lat1 = this.lat().toRad(), lon1 = this.lng().toRad();
var lat2 = point.lat().toRad(), lon2 = point.lng().toRad();
var dLat = lat2 - lat1;
var dLon = lon2 - lon1;
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1) * Math.cos(lat2) *
Math.sin(dLon/2) * Math.sin(dLon/2);
return 6371 * (2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)));
}
var i;
var j;
var distanceToNextPoint;
var bearingToNextPoint;
var radius;
var radiusIncrement;
var distanceStep = 50; // Render an intermediate circle every 50km.
var mapOptions = {
mapTypeId: google.maps.MapTypeId.TERRAIN,
center: new google.maps.LatLng(28.50, -81.50),
zoom: 5
};
var map = new google.maps.Map(document.getElementById("map"), mapOptions);
var pathPoints = [
new google.maps.LatLng(25.48, -71.26),
new google.maps.LatLng(25.38, -73.70),
new google.maps.LatLng(25.28, -77.00),
new google.maps.LatLng(25.24, -80.11),
new google.maps.LatLng(25.94, -82.71),
new google.maps.LatLng(27.70, -87.14)
];
pathPoints[0].radius = 80;
pathPoints[1].radius = 100;
pathPoints[2].radius = 200;
pathPoints[3].radius = 300;
pathPoints[4].radius = 350;
pathPoints[5].radius = 550;
new google.maps.Polyline({
path: pathPoints,
strokeColor: '#00FF00',
strokeOpacity: 1.0,
strokeWeight: 3,
map: map
});
for (i = 0; i < pathPoints.length; i++) {
new google.maps.Circle({
center: pathPoints[i],
radius: pathPoints[i].radius * 1000,
fillColor: '#FF0000',
fillOpacity: 0.2,
strokeOpacity: 0.5,
strokeWeight: 0,
map: map
});
if (i < (pathPoints.length - 1)) {
distanceToNextPoint = pathPoints[i].distanceTo(pathPoints[i + 1]);
bearingToNextPoint = pathPoints[i].bearingTo(pathPoints[i + 1]);
radius = pathPoints[i].radius;
radiusIncrement = (pathPoints[i + 1].radius - radius) /
(distanceToNextPoint / distanceStep);
for (j = distanceStep;
j < distanceToNextPoint;
j += distanceStep, radius += radiusIncrement) {
new google.maps.Circle({
center: pathPoints[i].destinationPoint(bearingToNextPoint, j),
radius: radius * 1000,
fillColor: '#FF0000',
fillOpacity: 0.2,
strokeWeight: 0,
map: map
});
}
}
}
</script>
</body>
</html>

If it's always a string of circles along a line, you could process a pair of adjacent circles at a time, find the two lines that are tangents to both of them and connect them up by their intersections to make one contiguous path. Add some interpolated bezier control points for smoothness.
This might break if your string of circles isn't as neat as the one in your first post (lots of overlap, circles inside circles, etc), but it's a start.

Related

How do I find a point at a given distance along a route? epoly.js is giving me extremely inaccurate and unusable results

I'm trying to find the point along a Google Maps API Directions route given a distance from the starting point. I have my code working and it gives me very accurate results most of the time. However when I make very long directions requests (for example, 1,000+ kilometers), the results are less accurate, and the longer the directions route the more inaccurate the results are. Once I reach approximately 3,000 kilometers the results are off by about 4,000 meters, which is entirely unacceptable for my application.
The function I'm using to compute the point is from epoly.js, and the code is as follows:
google.maps.Polyline.prototype.GetPointAtDistance = function(metres) {
if (metres == 0) return this.getPath().getAt(0);
if (metres < 0) return null;
if (this.getPath().getLength() < 2) return null;
var dist=0;
var olddist=0;
for (var i=1; (i < this.getPath().getLength() && dist < metres); i++) {
olddist = dist;
dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i-1));
}
if (dist < metres) {
return null;
}
var p1= this.getPath().getAt(i-2);
var p2= this.getPath().getAt(i-1);
var m = (metres-olddist)/(dist-olddist);
return new google.maps.LatLng( p1.lat() + (p2.lat()-p1.lat())*m, p1.lng() + (p2.lng()-p1.lng())*m);
}
This epoly.js function has this dependency function:
google.maps.LatLng.prototype.distanceFrom = function(newLatLng) {
var EarthRadiusMeters = 6378137.0;
var lat1 = this.lat();
var lon1 = this.lng();
var lat2 = newLatLng.lat();
var lon2 = newLatLng.lng();
var dLat = (lat2-lat1) * Math.PI / 180;
var dLon = (lon2-lon1) * Math.PI / 180;
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1 * Math.PI / 180 ) * Math.cos(lat2 * Math.PI / 180 ) *
Math.sin(dLon/2) * Math.sin(dLon/2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = EarthRadiusMeters * c;
return d;
}
What is causing the result to be so inaccurate over large distances and what can I do to make it more accurate? (I need accuracy down to approximately 1-3 meters) Does the Google Maps API have any way to do this or is epoly.js my best bet? If so, then what can I change about the above code to make it give more accurate results?
I've been searching for an answer to this for a while, but everything I can find either recommends epoly.js or shows code snippets that perform exactly the same computations as epoly.js. It seems as though Google doesn't have any built-in way to do this, however I've seen something similar done in some applications like https://routeview.org, where you can clearly see the orange man tracing along the route perfectly even when navigating thousands of kilometers at a time, so I have to believe a higher level of accuracy is possible.
Here are two screenshots illustrating a short distance request and a long distance request. Note that it's very accurate over short distances but becomes wildly inaccurate over longer distances. Also note that the marker in the second image is being viewed from far away. It may look close to the path, but it's actually about 5,000 meters away on the other side of a large hill. (The marker in the first image is viewed from very close up, and even when viewed so closely it doesn't deviate from the path any noticeable amount)
This image is for a 20km route:
This image is for a 3326km route:
Here is code that demonstrated my issue. Zoom in on the marker to see that it misses the route by a long shot.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Example</title>
</head>
<body>
<div id="map" style="width: 600px;height: 600px;"></div>
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY_HERE&v=weekly&channel=2"></script>
<script>
google.maps.LatLng.prototype.distanceFrom = function(newLatLng) {
var EarthRadiusMeters = 6378137.0;
var lat1 = this.lat();
var lon1 = this.lng();
var lat2 = newLatLng.lat();
var lon2 = newLatLng.lng();
var dLat = (lat2-lat1) * Math.PI / 180;
var dLon = (lon2-lon1) * Math.PI / 180;
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1 * Math.PI / 180 ) * Math.cos(lat2 * Math.PI / 180 ) *
Math.sin(dLon/2) * Math.sin(dLon/2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = EarthRadiusMeters * c;
return d;
}
google.maps.Polyline.prototype.GetPointAtDistance = function(metres) {// Stolen from http://www.geocodezip.com/scripts/v3_epoly.js
if (metres == 0) return this.getPath().getAt(0);
if (metres < 0) return null;
if (this.getPath().getLength() < 2) return null;
var dist=0;
var olddist=0;
for (var i=1; (i < this.getPath().getLength() && dist < metres); i++) {
olddist = dist;
dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i-1));
}
if (dist < metres) {
return null;
}
var p1= this.getPath().getAt(i-2);
var p2= this.getPath().getAt(i-1);
var m = (metres-olddist)/(dist-olddist);
return new google.maps.LatLng( p1.lat() + (p2.lat()-p1.lat())*m, p1.lng() + (p2.lng()-p1.lng())*m);
}
</script>
<script>
const map = new google.maps.Map(document.getElementById("map"),{
center: { lat: 37, lng: -100 },
zoom: 4,
clickableIcons: false
});
const directionsRenderer = new google.maps.DirectionsRenderer();
directionsRenderer.setMap(map);
const directions = new google.maps.DirectionsService();
directions.route({
origin: "seattle, wa",
destination: "chicago, il",
travelMode: "DRIVING"
},(data) => {
const polyline = new google.maps.Polyline({
path: data.routes[0].overview_path
});
new google.maps.Marker({
map: map,
position: polyline.GetPointAtDistance(400000)
});
directionsRenderer.setDirections(data);
});
</script>
</body>
</html>
(You'll need to provide your own API key)
EDIT:
I found out that the issue is on Google's end. Google is giving me a "close enough" polyline. Epoly.js is tracing that polyline perfectly, but that polyline simply doesn't line up with the route itself. This picture demonstrates the issue:
The dark blue line is Google's "close enough" polyline, and the light blue line is where the actual route is.
Just thought I'd leave this here for anybody else who's confused by this in the future.
Don't use the overview_path for long paths, it won't be accurate for close zoom levels. Use the concatenated steps polylines:
var legs = response.routes[0].legs;
for (i=0;i<legs.length;i++) {
var steps = legs[i].steps;
for (j=0;j<steps.length;j++) {
var nextSegment = steps[j].path;
for (k=0;k<nextSegment.length;k++) {
polyline.getPath().push(nextSegment[k]);
}
}
}
live example
Your original code (using the overview_path):
<!DOCTYPE html>
<html lang="en">
<head>
<title>Example</title>
</head>
<body>
<div id="map" style="width: 600px;height: 600px;"></div>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&v=weekly&channel=2"></script>
<script>
google.maps.LatLng.prototype.distanceFrom = function(newLatLng) {
var EarthRadiusMeters = 6378137.0;
var lat1 = this.lat();
var lon1 = this.lng();
var lat2 = newLatLng.lat();
var lon2 = newLatLng.lng();
var dLat = (lat2-lat1) * Math.PI / 180;
var dLon = (lon2-lon1) * Math.PI / 180;
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1 * Math.PI / 180 ) * Math.cos(lat2 * Math.PI / 180 ) *
Math.sin(dLon/2) * Math.sin(dLon/2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = EarthRadiusMeters * c;
return d;
}
google.maps.Polyline.prototype.GetPointAtDistance = function(metres) {// Stolen from http://www.geocodezip.com/scripts/v3_epoly.js
if (metres == 0) return this.getPath().getAt(0);
if (metres < 0) return null;
if (this.getPath().getLength() < 2) return null;
var dist=0;
var olddist=0;
for (var i=1; (i < this.getPath().getLength() && dist < metres); i++) {
olddist = dist;
dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i-1));
}
if (dist < metres) {
return null;
}
var p1= this.getPath().getAt(i-2);
var p2= this.getPath().getAt(i-1);
var m = (metres-olddist)/(dist-olddist);
return new google.maps.LatLng( p1.lat() + (p2.lat()-p1.lat())*m, p1.lng() + (p2.lng()-p1.lng())*m);
}
</script>
<script>
const map = new google.maps.Map(document.getElementById("map"),{
center: { lat: 37, lng: -100 },
zoom: 14,
clickableIcons: false
});
const directionsRenderer = new google.maps.DirectionsRenderer({preserveViewport: true});
directionsRenderer.setMap(map);
const directions = new google.maps.DirectionsService();
directions.route({
origin: "seattle, wa",
destination: "chicago, il",
travelMode: "DRIVING"
},(data) => {
const polyline = new google.maps.Polyline({
path: data.routes[0].overview_path
});
var marker = new google.maps.Marker({
map: map,
position: polyline.GetPointAtDistance(400000)
});
map.setCenter(marker.getPosition());
directionsRenderer.setDirections(data);
});
</script>
</body>
</html>
updated code snippet using the detailed path:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Example</title>
</head>
<body>
<div id="map" style="width: 600px;height: 600px;"></div>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&v=weekly&channel=2"></script>
<script>
google.maps.LatLng.prototype.distanceFrom = function(newLatLng) {
var EarthRadiusMeters = 6378137.0;
var lat1 = this.lat();
var lon1 = this.lng();
var lat2 = newLatLng.lat();
var lon2 = newLatLng.lng();
var dLat = (lat2-lat1) * Math.PI / 180;
var dLon = (lon2-lon1) * Math.PI / 180;
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1 * Math.PI / 180 ) * Math.cos(lat2 * Math.PI / 180 ) *
Math.sin(dLon/2) * Math.sin(dLon/2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = EarthRadiusMeters * c;
return d;
}
google.maps.Polyline.prototype.GetPointAtDistance = function(metres) {// Stolen from http://www.geocodezip.com/scripts/v3_epoly.js
if (metres == 0) return this.getPath().getAt(0);
if (metres < 0) return null;
if (this.getPath().getLength() < 2) return null;
var dist=0;
var olddist=0;
for (var i=1; (i < this.getPath().getLength() && dist < metres); i++) {
olddist = dist;
dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i-1));
}
if (dist < metres) {
return null;
}
var p1= this.getPath().getAt(i-2);
var p2= this.getPath().getAt(i-1);
var m = (metres-olddist)/(dist-olddist);
return new google.maps.LatLng( p1.lat() + (p2.lat()-p1.lat())*m, p1.lng() + (p2.lng()-p1.lng())*m);
}
</script>
<script>
const map = new google.maps.Map(document.getElementById("map"),{
center: { lat: 37, lng: -100 },
zoom: 14,
clickableIcons: false
});
const directionsRenderer = new google.maps.DirectionsRenderer({preserveViewport: true});
directionsRenderer.setMap(map);
const directions = new google.maps.DirectionsService();
directions.route({
origin: "seattle, wa",
destination: "chicago, il",
travelMode: "DRIVING"
},(data) => {
const polyline = new google.maps.Polyline();
var legs = data.routes[0].legs;
for (i=0;i<legs.length;i++) {
var steps = legs[i].steps;
for (j=0;j<steps.length;j++) {
var nextSegment = steps[j].path;
for (k=0;k<nextSegment.length;k++) {
polyline.getPath().push(nextSegment[k]);
}
}
}
var marker = new google.maps.Marker({
map: map,
position: polyline.GetPointAtDistance(400000)
});
map.setCenter(marker.getPosition());
directionsRenderer.setDirections(data);
});
</script>
</body>
</html>
result using the more detailed path (not the overview_path):

why is this formula for a circle giving me an ellipsoid in Javascript but a circle in Python?

I adapted the following code for python found on this page:
for a Javascript equivalent.
import math
# inputs
radius = 1000.0 # m - the following code is an approximation that stays reasonably accurate for distances < 100km
centerLat = 30.0 # latitude of circle center, decimal degrees
centerLon = -100.0 # Longitude of circle center, decimal degrees
# parameters
N = 10 # number of discrete sample points to be generated along the circle
# generate points
circlePoints = []
for k in xrange(N):
# compute
angle = math.pi*2*k/N
dx = radius*math.cos(angle)
dy = radius*math.sin(angle)
point = {}
point['lat']=centerLat + (180/math.pi)*(dy/6378137)
point['lon']=centerLon + (180/math.pi)*(dx/6378137)/math.cos(centerLat*math.pi/180)
# add to list
circlePoints.append(point)
print circlePoints
The distance between these points is constant, as it should be.
My JS version is, as far as I know, equivalent:
var nodesCount = 8;
var coords = [];
for (var i = 0; i <= nodesCount; i++) {
var radius = 1000;
var angle = Math.PI*2*i/nodesCount;
var dx = radius*Math.cos(angle);
var dy = radius*Math.sin(angle);
coords.push([(rootLongitude + (180 / Math.PI) * (dx / EARTH_RADIUS) / Math.cos(rootLatitude * Math.PI / 180)),(rootLatitude + (180 / Math.PI) * (dy / EARTH_RADIUS))]);
}
But when I output this, the coordinates are not equidistant from the center.
This is enormously frustrating -- I've been trying to debug this for a day. Can anyone see what's making the JS code fail?
You somehow got lat/lon reversed.
var linkDistance = 10; //$('#linkDistance').val();
var nodesCount = 8;
var bandwidth = "10 GB/s";
var centerLat = 35.088878;
var centerLon = -106.65262;
var EARTH_RADIUS = 6378137;
var mymap = L.map('mapid').setView([centerLat, centerLon], 11);
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpandmbXliNDBjZWd2M2x6bDk3c2ZtOTkifQ._QA7i5Mpkd_m30IGElHziw', {
maxZoom: 18,
attribution: 'Map data © OpenStreetMap contributors, ' +
'CC-BY-SA, ' +
'Imagery © Mapbox',
id: 'mapbox.streets'
}).addTo(mymap);
function drawNext(centerLat, centerLon) {
var coords = [];
for (var i = 0; i < nodesCount; i++) {
var radius = linkDistance * 1000;
var angle = Math.PI * 2 * i / nodesCount;
var dx = radius * Math.cos(angle);
var dy = radius * Math.sin(angle);
var lat = centerLon + (180 / Math.PI) * (dy / 6378137);
var lon = centerLat + (180 / Math.PI) * (dx / 6378137) / Math.cos(centerLon * Math.PI / 180);
coords.push([lat, lon]);
}
for (var i = 0; i < coords.length; i++) {
new L.Circle(coords[i], 500, {
color: 'black',
fillColor: '#f03',
fillOpacity: 0.1
}).addTo(mymap);
console.log("added circle to: " + coords[i]);
}
}
drawNext(centerLon, centerLat);
var popup = L.popup();
function onMapClick(e) {
popup
.setLatLng(e.latlng)
.setContent("You clicked the map at " + e.latlng.toString())
.openOn(mymap);
}
mymap.on('click', onMapClick);
#mapid {
height: 500px;
}
<script src="https://npmcdn.com/leaflet#1.0.0-rc.2/dist/leaflet-src.js"></script>
<link href="https://npmcdn.com/leaflet#1.0.0-rc.2/dist/leaflet.css" rel="stylesheet"/>
<div id="mapid"></div>

Binning data into a hexagonal grid in Google Maps

I'm trying to display geospatial data in a hexagonal grid on a Google Map.
In order to do so, given a hexagon tile grid size X I need to be able to convert ({lat, lng}) coordinates into the ({lat, lng}) centers of the hexagon grid tiles that contain them.
In the end, I would like to be able to display data on a Google Map like this:
Does anybody have any insight into how this is done?
I've tried porting this Python hexagon binning script, binner.py to Javascript but it doesn't seem to be working properly- the output values are all the same as the input ones.
For the sake of this example, I don't care if there are multiple polygons in a single location, I just need to figure out how to bin them into the correct coordinates.
Code below, (Plunker here!)
var map;
var pointCount = 0;
var locations = [];
var gridWidth = 200000; // hex tile size in meters
var bounds;
var places = [
[44.13, -69.51],
[45.23, -67.42],
[46.33, -66.53],
[44.43, -65.24],
[46.53, -64.15],
[44.63, -63.06],
[44.73, -62.17],
[43.83, -63.28],
[44.93, -64.39],
[44.13, -65.41],
[41.23, -66.52],
[44.33, -67.63],
[42.43, -68.74],
[44.53, -69.65],
[40.63, -70.97],
]
var SQRT3 = 1.73205080756887729352744634150587236;
$(document).ready(function(){
bounds = new google.maps.LatLngBounds();
map = new google.maps.Map(document.getElementById("map_canvas"), {center: {lat: 0, lng: 0}, zoom: 2});
// Adding a marker just so we can visualize where the actual data points are.
// In the end, we want to see the hex tile that contain them
places.forEach(function(place, p){
latlng = new google.maps.LatLng({lat: place[0], lng: place[1]});
marker = new google.maps.Marker({position: latlng, map: map})
// Fitting to bounds so the map is zoomed to the right place
bounds.extend(latlng);
});
map.fitBounds(bounds);
// Now, we draw our hexagons! (or try to)
locations = makeBins(places);
locations.forEach(function(place, p){
drawHorizontalHexagon(map, place, gridWidth);
})
});
function drawHorizontalHexagon(map,position,radius){
var coordinates = [];
for(var angle= 0;angle < 360; angle+=60) {
coordinates.push(google.maps.geometry.spherical.computeOffset(position, radius, angle));
}
// Construct the polygon.
var polygon = new google.maps.Polygon({
paths: coordinates,
position: position,
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: '#FF0000',
fillOpacity: 0.35,
geodesic: true
});
polygon.setMap(map);
}
// Below is my attempt at porting binner.py to Javascript.
// Source: https://github.com/coryfoo/hexbins/blob/master/hexbin/binner.py
function distance(x1, y1, x2, y2){
console.log(x1, y1, x2, y2);
result = Math.sqrt(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2)));
console.log("Distance: ", result);
return result;
}
function nearestCenterPoint(value, scale){
div = (value / (scale / 2));
mod = value % (scale / 2);
if(div % 2 == 1){
increment = 1;
} else {
increment = 0;
}
rounded = (scale / 2) * (div + increment);
if(div % 2 === 0){
increment = 1;
} else {
increment = 0;
}
rounded_scaled = (scale / 2) * (div + increment)
result = [rounded, rounded_scaled];
return result;
}
function makeBins(data){
bins = [];
data.forEach(function(place, p){
x = place[0];
y = place[1];
console.log("Original location:", x, y);
px_nearest = nearestCenterPoint(x, gridWidth);
py_nearest = nearestCenterPoint(y, gridWidth * SQRT3);
z1 = distance(x, y, px_nearest[0], py_nearest[0]);
z2 = distance(x, y, px_nearest[1], py_nearest[1]);
console.log(z1, z2);
if(z1 > z2){
bin = new google.maps.LatLng({lat: px_nearest[0], lng: py_nearest[0]});
console.log("Final location:", px_nearest[0], py_nearest[0]);
} else {
bin = new google.maps.LatLng({lat: px_nearest[1], lng: py_nearest[1]});
console.log("Final location:", px_nearest[1], py_nearest[1]);
}
bins.push(bin);
})
return bins;
}
Use google.maps.geometry.poly.containsLocation.
for (var i = 0; i < hexgrid.length; i++) {
if (google.maps.geometry.poly.containsLocation(place, hexgrid[i])) {
if (!hexgrid[i].contains) {
hexgrid[i].contains = 0;
}
hexgrid[i].contains++
}
}
Example based off this related question: How can I make a Google Maps API v3 hexagon tiled map, preferably coordinate-based?. The number in the white box in the center of each hexagon is the number of markers contained by it.
proof of concept fiddle
code snippet:
var map = null;
var hexgrid = [];
function initMap() {
var myOptions = {
zoom: 8,
center: new google.maps.LatLng(43, -79.5),
mapTypeControl: true,
mapTypeControlOptions: {
style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
},
navigationControl: true,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
map = new google.maps.Map(document.getElementById("map"),
myOptions);
createHexGrid();
var bounds = new google.maps.LatLngBounds();
// Seed our dataset with random locations
for (var i = 0; i < hexgrid.length; i++) {
var hexbounds = new google.maps.LatLngBounds();
for (var j = 0; j < hexgrid[i].getPath().getLength(); j++) {
bounds.extend(hexgrid[i].getPath().getAt(j));
hexbounds.extend(hexgrid[i].getPath().getAt(j));
}
hexgrid[i].bounds = hexbounds;
}
var span = bounds.toSpan();
var locations = [];
for (pointCount = 0; pointCount < 50; pointCount++) {
place = new google.maps.LatLng(Math.random() * span.lat() + bounds.getSouthWest().lat(), Math.random() * span.lng() + bounds.getSouthWest().lng());
bounds.extend(place);
locations.push(place);
var mark = new google.maps.Marker({
map: map,
position: place
});
// bin points in hexgrid
for (var i = 0; i < hexgrid.length; i++) {
if (google.maps.geometry.poly.containsLocation(place, hexgrid[i])) {
if (!hexgrid[i].contains) {
hexgrid[i].contains = 0;
}
hexgrid[i].contains++
}
}
}
// add labels
for (var i = 0; i < hexgrid.length; i++) {
if (typeof hexgrid[i].contains == 'undefined') {
hexgrid[i].contains = 0;
}
var labelText = "<div style='background-color:white'>" + hexgrid[i].contains + "</div>";
var myOptions = {
content: labelText,
boxStyle: {
border: "1px solid black",
textAlign: "center",
fontSize: "8pt",
width: "20px"
},
disableAutoPan: true,
pixelOffset: new google.maps.Size(-10, 0),
position: hexgrid[i].bounds.getCenter(),
closeBoxURL: "",
isHidden: false,
pane: "floatPane",
enableEventPropagation: true
};
var ibLabel = new InfoBox(myOptions);
ibLabel.open(map);
}
}
function createHexGrid() {
// === Hexagonal grid ===
var point = new google.maps.LatLng(42, -78.8);
map.setCenter(point);
var hex1 = google.maps.Polygon.RegularPoly(point, 25000, 6, 90, "#000000", 1, 1, "#00ff00", 0.5);
hex1.setMap(map);
var d = 2 * 25000 * Math.cos(Math.PI / 6);
hexgrid.push(hex1);
var hex30 = google.maps.Polygon.RegularPoly(EOffsetBearing(point, d, 30), 25000, 6, 90, "#000000", 1, 1, "#00ffff", 0.5);
hex30.setMap(map);
hexgrid.push(hex30);
var hex90 = google.maps.Polygon.RegularPoly(EOffsetBearing(point, d, 90), 25000, 6, 90, "#000000", 1, 1, "#ffff00", 0.5);
hex90.setMap(map);
hexgrid.push(hex90);
var hex150 = google.maps.Polygon.RegularPoly(EOffsetBearing(point, d, 150), 25000, 6, 90, "#000000", 1, 1, "#00ffff", 0.5);
hex150.setMap(map);
hexgrid.push(hex150);
var hex210 = google.maps.Polygon.RegularPoly(EOffsetBearing(point, d, 210), 25000, 6, 90, "#000000", 1, 1, "#ffff00", 0.5);
hex210.setMap(map);
hexgrid.push(hex210);
hex270 = google.maps.Polygon.RegularPoly(EOffsetBearing(point, d, 270), 25000, 6, 90, "#000000", 1, 1, "#ffff00", 0.5);
hex270.setMap(map);
hexgrid.push(hex270);
var hex330 = google.maps.Polygon.RegularPoly(EOffsetBearing(point, d, 330), 25000, 6, 90, "#000000", 1, 1, "#ffff00", 0.5);
hex330.setMap(map);
hexgrid.push(hex330);
var hex30_2 = google.maps.Polygon.RegularPoly(EOffsetBearing(EOffsetBearing(point, d, 30), d, 90), 25000, 6, 90, "#000000", 1, 1, "#ff0000", 0.5);
hex30_2.setMap(map);
hexgrid.push(hex30_2);
var hex150_2 = google.maps.Polygon.RegularPoly(EOffsetBearing(EOffsetBearing(point, d, 150), d, 90), 25000, 6, 90, "#000000", 1, 1, "#0000ff", 0.5);
hex150_2.setMap(map);
hexgrid.push(hex150_2);
var hex90_2 = google.maps.Polygon.RegularPoly(EOffsetBearing(EOffsetBearing(point, d, 90), d, 90), 25000, 6, 90, "#000000", 1, 1, "#00ff00", 0.5);
hex90_2.setMap(map);
hexgrid.push(hex90_2);
// This Javascript is based on code provided by the
// Community Church Javascript Team
// http://www.bisphamchurch.org.uk/
// http://econym.org.uk/gmap/
//]]>
}
google.maps.event.addDomListener(window, 'load', initMap);
// EShapes.js
//
// Based on an idea, and some lines of code, by "thetoy"
//
// This Javascript is provided by Mike Williams
// Community Church Javascript Team
// http://www.bisphamchurch.org.uk/
// http://econym.org.uk/gmap/
//
// This work is licenced under a Creative Commons Licence
// http://creativecommons.org/licenses/by/2.0/uk/
//
// Version 0.0 04/Apr/2008 Not quite finished yet
// Version 1.0 10/Apr/2008 Initial release
// Version 3.0 12/Oct/2011 Ported to v3 by Lawrence Ross
google.maps.Polygon.Shape = function(point, r1, r2, r3, r4, rotation, vertexCount, strokeColour, strokeWeight, Strokepacity, fillColour, fillOpacity, opts, tilt) {
var rot = -rotation * Math.PI / 180;
var points = [];
var latConv = google.maps.geometry.spherical.computeDistanceBetween(point, new google.maps.LatLng(point.lat() + 0.1, point.lng())) * 10;
var lngConv = google.maps.geometry.spherical.computeDistanceBetween(point, new google.maps.LatLng(point.lat(), point.lng() + 0.1)) * 10;
var step = (360 / vertexCount) || 10;
var flop = -1;
if (tilt) {
var I1 = 180 / vertexCount;
} else {
var I1 = 0;
}
for (var i = I1; i <= 360.001 + I1; i += step) {
var r1a = flop ? r1 : r3;
var r2a = flop ? r2 : r4;
flop = -1 - flop;
var y = r1a * Math.cos(i * Math.PI / 180);
var x = r2a * Math.sin(i * Math.PI / 180);
var lng = (x * Math.cos(rot) - y * Math.sin(rot)) / lngConv;
var lat = (y * Math.cos(rot) + x * Math.sin(rot)) / latConv;
points.push(new google.maps.LatLng(point.lat() + lat, point.lng() + lng));
}
return (new google.maps.Polygon({
paths: points,
strokeColor: strokeColour,
strokeWeight: strokeWeight,
strokeOpacity: Strokepacity,
fillColor: fillColour,
fillOpacity: fillOpacity
}))
}
google.maps.Polygon.RegularPoly = function(point, radius, vertexCount, rotation, strokeColour, strokeWeight, Strokepacity, fillColour, fillOpacity, opts) {
rotation = rotation || 0;
var tilt = !(vertexCount & 1);
return google.maps.Polygon.Shape(point, radius, radius, radius, radius, rotation, vertexCount, strokeColour, strokeWeight, Strokepacity, fillColour, fillOpacity, opts, tilt)
}
function EOffsetBearing(point, dist, bearing) {
var latConv = google.maps.geometry.spherical.computeDistanceBetween(point, new google.maps.LatLng(point.lat() + 0.1, point.lng())) * 10;
var lngConv = google.maps.geometry.spherical.computeDistanceBetween(point, new google.maps.LatLng(point.lat(), point.lng() + 0.1)) * 10;
var lat = dist * Math.cos(bearing * Math.PI / 180) / latConv;
var lng = dist * Math.sin(bearing * Math.PI / 180) / lngConv;
return new google.maps.LatLng(point.lat() + lat, point.lng() + lng)
}
html,
body,
#map {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px
}
<script src="https://maps.googleapis.com/maps/api/js?libraries=geometry&key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk"></script>
<script src="https://google-maps-utility-library-v3.googlecode.com/svn/trunk/infobox/src/infobox.js"></script>
<div id="map"></div>
Take a look at deck.gl and its hexagon layer
https://deck.gl/docs/api-reference/geo-layers/h3-hexagon-layer
With the getHexagon function, you can retrieve the H3 hexagon index of each object.
Then, you can use H3's cellToLatLng function to retrieve the hexagon cell's center coordinates.
But actually, you won't need this step if you just want to create a hexagonal data grid like the one you have shown in the image. You can simply feed the data into the deck.gl hexagon layer and the framework will do the rest to produce the output you want.

google maps circle to polyline coordinate array

how to get array of polyline coordinates from google.maps.Circle's object
there is no api doc entry about that
A google.maps.Circle doesn't contain an array of coordinates. If you want a google.maps.Polygon that is shaped like a circle, you need to make one.
function drawCircle(point, radius, dir) {
var d2r = Math.PI / 180; // degrees to radians
var r2d = 180 / Math.PI; // radians to degrees
var earthsradius = 3963; // 3963 is the radius of the earth in miles
var points = 32;
// find the raidus in lat/lon
var rlat = (radius / earthsradius) * r2d;
var rlng = rlat / Math.cos(point.lat() * d2r);
var extp = new Array();
if (dir==1) {
var start=0;
var end=points+1; // one extra here makes sure we connect the path
} else {
var start=points+1;
var end=0;
}
for (var i=start; (dir==1 ? i < end : i > end); i=i+dir)
{
var theta = Math.PI * (i / (points/2));
ey = point.lng() + (rlng * Math.cos(theta)); // center a + radius x * cos(theta)
ex = point.lat() + (rlat * Math.sin(theta)); // center b + radius y * sin(theta)
extp.push(new google.maps.LatLng(ex, ey));
}
return extp;
}
var circle = new google.maps.Polygon({
map: map,
paths: [drawCircle(new google.maps.LatLng(-33.9,151.2), 100, 1)],
strokeColor: "#0000FF",
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: "#FF0000",
fillOpacity: 0.35
});
Example
code snippet:
function drawCircle(point, radius, dir) {
var d2r = Math.PI / 180; // degrees to radians
var r2d = 180 / Math.PI; // radians to degrees
var earthsradius = 3963; // 3963 is the radius of the earth in miles
var points = 32;
// find the raidus in lat/lon
var rlat = (radius / earthsradius) * r2d;
var rlng = rlat / Math.cos(point.lat() * d2r);
var extp = new Array();
if (dir == 1) {
var start = 0;
var end = points + 1
} // one extra here makes sure we connect the
else {
var start = points + 1;
var end = 0
}
for (var i = start;
(dir == 1 ? i < end : i > end); i = i + dir) {
var theta = Math.PI * (i / (points / 2));
ey = point.lng() + (rlng * Math.cos(theta)); // center a + radius x * cos(theta)
ex = point.lat() + (rlat * Math.sin(theta)); // center b + radius y * sin(theta)
extp.push(new google.maps.LatLng(ex, ey));
bounds.extend(extp[extp.length - 1]);
}
// alert(extp.length);
return extp;
}
var map = null;
var bounds = null;
function initialize() {
var myOptions = {
zoom: 10,
center: new google.maps.LatLng(-33.9, 151.2),
mapTypeControl: true,
mapTypeControlOptions: {
style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
},
navigationControl: true,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
map = new google.maps.Map(document.getElementById("map_canvas"),
myOptions);
bounds = new google.maps.LatLngBounds();
var donut = new google.maps.Polygon({
paths: [drawCircle(new google.maps.LatLng(-33.9, 151.2), 100, 1),
drawCircle(new google.maps.LatLng(-33.9, 151.2), 50, -1)
],
strokeColor: "#0000FF",
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: "#FF0000",
fillOpacity: 0.35
});
donut.setMap(map);
map.fitBounds(bounds);
}
google.maps.event.addDomListener(window, 'load', initialize);
html,
body,
#map_canvas {
width: 100%;
height: 100%;
padding: 0px;
margin: 0px;
}
<script src="https://maps.google.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk"></script>
<div id="map_canvas"></div>
I implement this inside autocomplete, but, can't understand this: var points = 32; how calculate this variable?
createAutocomplete() {
var drawer = this.drawCircle;
var MAPS = window.Heatmap.Maps;
const center = MAPS.map.center;
const defaultBounds = {
north: center.lat + 0.1,
south: center.lat - 0.1,
east: center.lng + 0.1,
west: center.lng - 0.1,
};
let input = document.getElementById("search");
const options = {
bounds: defaultBounds,
componentRestrictions: {country: "us"},
fields: ["address_components", "geometry", "icon", "name"],
origin: MAPS.map.center,
strictBounds: false,
types: ['(cities)'],
};
MAPS.autocomplete = new google.maps.places.Autocomplete(input, options);
google.maps.event.addListener(MAPS.autocomplete, 'place_changed', function () {
const selectedIndex = document.getElementById("distance").selectedIndex;
let distance = document.getElementsByTagName("option")[selectedIndex].value;
if (MAPS.circle.instance) {
MAPS.circle.instance.setMap(null);
MAPS.marker.collection['searchMarker'].setMap(null);
}
let place = MAPS.autocomplete.getPlace();
var center = {lat: place.geometry.location.lat(), lng: place.geometry.location.lng()};
MAPS.map.instance.panTo(center);
MAPS.map.instance.setZoom(7);
MAPS.marker.collection['searchMarker'] = new google.maps.Marker({
position: center,
map: MAPS.map.instance,
icon: {
url: "http://maps.google.com/mapfiles/ms/icons/yellow-dot.png"
}
});
MAPS.circle.instance = new google.maps.Polygon({
map: MAPS.map.instance,
paths: [drawer(center, distance, 1)],
strokeColor: "#0000FF",
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: "#FF0000",
fillOpacity: 0.35
});
console.log(MAPS.circle.instance.getPath())
});
}
drawCircle(point, radius, dir) {
var d2r = Math.PI / 180;
var r2d = 180 / Math.PI;
var earthsradius = 3963;
var points = 32;
var rlat = (radius / earthsradius) * r2d;
var rlng = rlat / Math.cos(point.lat * d2r);
var extp = [];
var start = null;
var end = null;
if (dir === 1) {
start = 0;
end = points + 1;
} else {
start = points + 1;
end = 0;
}
for (var i = start; (dir === 1 ? i < end : i > end); i = i + dir) {
var theta = Math.PI * (i / (points / 2));
var ey = point.lng + (rlng * Math.cos(theta));
var ex = point.lat + (rlat * Math.sin(theta));
extp.push(new google.maps.LatLng(ex, ey));
}
return extp;
}

Draw Rectangles in Area on Google Maps

I'm drawing an area on Google Maps using the Geometry API. I want to know if it is possible to draw a repeating element onto an area that is dynamic in size?
For example, if I draw my area to look like this:
Then I want to be able to hit 'Next Step' and see something like this, with the rectangles drawn in the area, but only if they will fit. i.e., they have to be 'full' rectangles, not part rectangles:
The only problem is, I'm not entirely sure how to go about this. I would use HTML5 <canvas> but unfortunately, this needs to be as browser-friendly as possible, but if it has to be <canvas> then so be it!
Although I didn't use canvas, how about this code?
function onPolygonComplete(polygon) {
var bounds, paths, sw, ne, ystep, xstep,
boxH, boxW, posArry, flag, pos,
x, y, i, box, maxBoxCnt;
//Delete old boxes.
boxes.forEach(function(box, i) {
box.setMap(null);
delete box;
});
//Calculate the bounds that contains entire polygon.
bounds = new google.maps.LatLngBounds();
paths = polygon.getPath();
paths.forEach(function(latlng, i){
bounds.extend(latlng);
});
//Calculate the small box size.
maxBoxCnt = 8;
sw = bounds.getSouthWest();
ne = bounds.getNorthEast();
ystep = Math.abs(sw.lat() - ne.lat()) / maxBoxCnt;
boxH = Math.abs(sw.lat() - ne.lat()) / (maxBoxCnt + 1);
xstep = Math.abs(sw.lng() - ne.lng()) / maxBoxCnt;
boxW = Math.abs(sw.lng() - ne.lng()) / (maxBoxCnt + 1);
for (y = 0; y < maxBoxCnt; y++) {
for (x = 0; x < maxBoxCnt; x++) {
//Detect that polygon is able to contain a small box.
bounds = new google.maps.LatLngBounds();
posArry = [];
posArry.push(new google.maps.LatLng(sw.lat() + ystep * y, sw.lng() + xstep * x));
posArry.push(new google.maps.LatLng(sw.lat() + ystep * y, sw.lng() + xstep * x + boxW));
posArry.push(new google.maps.LatLng(sw.lat() + ystep * y + boxH, sw.lng() + xstep * x));
posArry.push(new google.maps.LatLng(sw.lat() + ystep * y + boxH, sw.lng() + xstep * x + boxW));
flag = true;
for (i = 0; i < posArry.length; i++) {
pos = posArry[i];
if (flag) {
flag = google.maps.geometry.poly.containsLocation(pos, polygon);
bounds.extend(pos);
}
}
//Draw a small box.
if (flag) {
box = new google.maps.Rectangle({
bounds : bounds,
map : mapCanvas,
strokeColor: '#00ffff',
strokeOpacity: 0.5,
strokeWeight: 1,
fillColor: '#00ffff',
fillOpacity : 0.5,
clickable: false
});
boxes.push(box);
}
}
}
}
This code works like this image.
I wrote a page that explains the code.
http://googlemaps.googlermania.com/google_maps_api_v3/en/poly_containsLocation.html
#Joshua M
Sorry for keeping you wait.
Ok, the new code is below.
You can specify the small box size at var boxSize = new google.maps.Size(10, 20);
<!DOCTYPE html>
<html>
<head>
<script src="http://maps.googleapis.com/maps/api/js?sensor=false&libraries=geometry"></script>
<script type='text/javascript'>
var mapCanvas, boxes = new google.maps.MVCArray();
function initialize() {
var mapDiv = document.getElementById("map_canvas");
mapCanvas = new google.maps.Map(mapDiv, {
center : new google.maps.LatLng(37.422191,-122.084585),
mapTypeId : google.maps.MapTypeId.SATELLITE,
zoom : 19,
tilt : 0
});
//Encoded path
var encodedPath = "eblcFnuchVv#D#q#P?a#eD]AC~#b#DCz#a#A";
var points = google.maps.geometry.encoding.decodePath(encodedPath);
//Draw a polygon
var polygonOpts = {
paths : points,
strokeWeight : 6,
strokeColor : "#FF0000",
strokeOpacity : 1,
//fillColor : "blue",
fillOpacity : 0,
map : mapCanvas,
editable : true
};
var poly = new google.maps.Polygon(polygonOpts);
var proc = function() {
onPolygonComplete(poly);
};
google.maps.event.addListener(mapCanvas, "projection_changed", proc);
google.maps.event.addListener(poly.getPath(), 'insert_at', proc);
google.maps.event.addListener(poly.getPath(), 'remove_at', proc);
google.maps.event.addListener(poly.getPath(), 'set_at', proc);
}
function onDrawMgr_complete(polygon) {
var path = polygon.getPath();
console.log(google.maps.geometry.encoding.encodePath(path));
}
function onPolygonComplete(polygon) {
var bounds, paths, sw, ne, ystep, xstep, boxH, boxW, posArry, flag, pos, x, y, i, box;
//Delete old boxes.
boxes.forEach(function(box, i) {
box.setMap(null);
delete box;
});
//Calculate the bounds that contains entire polygon.
bounds = new google.maps.LatLngBounds();
paths = polygon.getPath();
paths.forEach(function(latlng, i) {
bounds.extend(latlng);
});
var projection = mapCanvas.getProjection();
var zoom = mapCanvas.getZoom();
var powBase = Math.pow(2, zoom);
//Calculate the small box size.
sw = bounds.getSouthWest();
ne = bounds.getNorthEast();
var swPoint = projection.fromLatLngToPoint(sw);
var nePoint = projection.fromLatLngToPoint(ne);
var boxSize = new google.maps.Size(10, 20); //in pixels.
boxSize.width /= powBase;
boxSize.height /= powBase;
var maxX = Math.floor(Math.abs((swPoint.x - nePoint.x)) / boxSize.width);
var maxY = Math.floor(Math.abs((swPoint.y - nePoint.y)) / boxSize.height);
for ( y = 0; y < maxY; y++) {
for (x = 0; x < maxX; x++) {
//Detect that polygon is able to contain a small box.
bounds = new google.maps.LatLngBounds();
posArry = [];
posArry.push(new google.maps.Point(swPoint.x + boxSize.width * x, swPoint.y - boxSize.height * y));
posArry.push(new google.maps.Point(swPoint.x + boxSize.width * x, swPoint.y - boxSize.height * (y + 1)));
posArry.push(new google.maps.Point(swPoint.x + boxSize.width * (x + 1), swPoint.y - boxSize.height * y));
posArry.push(new google.maps.Point(swPoint.x + boxSize.width * (x + 1), swPoint.y - boxSize.height * (y + 1)));
var flag = true;
for (var i = 0; i < posArry.length; i++) {
pos = projection.fromPointToLatLng(posArry[i]);
if (flag) {
flag = google.maps.geometry.poly.containsLocation(pos, polygon);
bounds.extend(pos);
}
}
if (flag) {
box = new google.maps.Rectangle({
bounds : bounds,
map : mapCanvas,
strokeColor : 'green',
strokeOpacity : 1,
strokeWeight : 1,
fillColor : 'yellow',
fillOpacity : 0.5,
clickable : false
});
boxes.push(box);
}
}
}
}
google.maps.event.addDomListener(window, "load", initialize);
</script>
<style type="text/css">
window,html,#map_canvas{
width : 700px;
height : 500px;
}
</style>
</head>
<body>
<div id="map_canvas"/>
</body>
</html>

Categories