Adding many circles to a google map - google-maps

I'm attempting to draw many circles (around 1000) on a google map, at various locations and sizes, and then binding a click event to them. But this many calls of new google.maps.Circle(parameters); makes the page loading slow, and sometimes it just hangs forever, so I'd like to figure out if there's a better/faster way to accomplish what I'm trying to do.
I see that there's something called a kml layer, but there doesn't appear to be any easy way to draw filled circles with it and I'm not sure if I can still bind click events to each individual circle in the layer or not.
Looking at the circle workaround on the google KML faq page, I'm not sure if generating a KML file that contains thousands of circles similar to this would end up saving any time.
I also have no idea how to go about generating this kml file.
Finally, take into consideration that I'm pulling the circles I'm attempting to draw from a database, so I'd either have to generate the KML file on the fly for use or generate a new file every time a circle is removed or added from the DB so that the file stays up to date.
Of course, if there's another alternative, I'm all ears!

With the help of others via the Google Maps API v3 Group, I was able to implement a map overlay that handles 10,000 points suprisingly well. The trick is to use a canvas tile overlay, which minimizes the number of DOM elements in exchange for much simpler/lighter-weight POJsOs (plain old JavaScript objects).
Demo page w/mouse click events (API v2 only): http://notebook.kulchenko.com/maps/datamark
Demo page w/cursor swapping (API v2 and v3): http://notebook.kulchenko.com/maps/gridmark

Here is yet another example that demonstrates how to render multiple objects on Google Map using Overlay approach. Since the performance could degrade considerably while the amount of objects (e.g. google.maps.Circle) is increasing, it is proposed to render objects using canvas element instead of divone.
Example
The example demonstrates how to render 1k objects (cities)
var overlay;
USCitiesOverlay.prototype = new google.maps.OverlayView();
function USCitiesOverlay(map) {
this._map = map;
this._cities = [];
this._radius = 6;
this._container = document.createElement("div");
this._container.id = "citieslayer";
this.setMap(map);
this.addCity = function (lat, lng,population) {
this._cities.push({position: new google.maps.LatLng(lat,lng),population: population});
};
}
USCitiesOverlay.prototype.createCityIcon = function (id,pos,population) {
var cityIcon = document.createElement('canvas');
cityIcon.id = 'cityicon_' + id;
//calculate radius based on poulation
this._radius = population / 100000;
cityIcon.width = cityIcon.height = this._radius * 2;
cityIcon.style.width = cityIcon.width + 'px';
cityIcon.style.height = cityIcon.height + 'px';
cityIcon.style.left = (pos.x - this._radius) + 'px';
cityIcon.style.top = (pos.y - this._radius) + 'px';
cityIcon.style.position = "absolute";
var centerX = cityIcon.width / 2;
var centerY = cityIcon.height / 2;
var ctx = cityIcon.getContext('2d');
ctx.fillStyle = 'rgba(160,16,0,0.6)';
ctx.beginPath();
ctx.arc(centerX, centerY, this._radius, 0, Math.PI * 2, true);
ctx.fill();
return cityIcon;
};
USCitiesOverlay.prototype.ensureCityIcon = function (id,pos,population) {
var cityIcon = document.getElementById("cityicon_" + id);
if(cityIcon){
cityIcon.style.left = (pos.x - this._radius) + 'px';
cityIcon.style.top = (pos.y - this._radius) + 'px';
return cityIcon;
}
return this.createCityIcon(id,pos,population);
};
USCitiesOverlay.prototype.onAdd = function () {
var panes = this.getPanes();
panes.overlayLayer.appendChild(this._container);
};
USCitiesOverlay.prototype.draw = function () {
var zoom = this._map.getZoom();
var overlayProjection = this.getProjection();
var container = this._container;
this._cities.forEach(function(city,idx){
var xy = overlayProjection.fromLatLngToDivPixel(city.position);
var cityIcon = overlay.ensureCityIcon(idx,xy,city.population);
container.appendChild(cityIcon);
});
};
USCitiesOverlay.prototype.onRemove = function () {
this._container.parentNode.removeChild(this._container);
this._container = null;
};
function getRandomInterval(min, max) {
return Math.random() * (max - min) + min;
}
function generateCityMap(count) {
var citymap = [];
var minPos = new google.maps.LatLng(49.25, -123.1);
var maxPos = new google.maps.LatLng(34.052234, -74.005973);
for(var i = 0; i < count;i++)
{
var lat = getRandomInterval(minPos.lat(),maxPos.lat());
var lng = getRandomInterval(minPos.lng(),maxPos.lng());
var population = getRandomInterval(100000,1000000);
citymap.push({
location: new google.maps.LatLng(lat, lng),
population: population
});
}
return citymap;
}
function initialize() {
var mapOptions = {
zoom: 4,
center: new google.maps.LatLng(37.09024, -95.712891),
mapTypeId: google.maps.MapTypeId.TERRAIN
};
var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
overlay = new USCitiesOverlay(map);
//overlay.addCity(40.714352, -74.005973); //chicago
//overlay.addCity(40.714352, -74.005973); //newyork
//overlay.addCity(34.052234, -118.243684); //losangeles
//overlay.addCity(49.25, -123.1); //vancouver
var citymap = generateCityMap(1000);
citymap.forEach(function(city){
overlay.addCity(city.location.lat(), city.location.lng(),city.population);
});
}
google.maps.event.addDomListener(window, 'load', initialize);
html, body, #map-canvas {
height: 100%;
margin: 0px;
padding: 0px;
}
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&signed_in=true"></script>
<div id="map-canvas"></div>

Forget about KML, custom tiles are the way to go.
Have a look at these county maps:
http://maps.forum.nu/v3/gm_customTiles.html (check the "Density" box).
and
http://maps.forum.nu/gm_main.html?lat=31.428663&lon=-110.830078&z=4&mType=10
(click on the map to get county info)
These maps have 3000+ polygons, (not circles), and they load fast. The first link is API V3, the second is API V2.
The second map (V2) has click events. The click event handler is attached to the map itself, and it sends an AJAX call to the server with the lat/lon of the click. The server side script then looks this lat/lon up in the database to determine which county was clicked.

Related

Alternative for point.x for google map api v3

This is a portion of code I'm working on: (this legacy code is drawing Circle using Polygon paths:
GEvent.addListener(bigmap_rad, 'click', function(overlay, cpoint) {
var radius = document.getElementById('circle_radius').value;
var c_center = new GLatLng(cpoint.y,cpoint.x);
var c_marker = new GMarker(c_center);
var latOffset = 0.01;
var lonOffset = 0.01;
var latConv = c_center.distanceFrom(new GLatLng(c_center.lat()+0.1, c_center.lng()))/100;
var lngConv = c_center.distanceFrom(new GLatLng(c_center.lat(), c_center.lng()+0.1))/100;
// nodes = number of points to create polygon
var nodes = 40;
// Create an array of points
var cpoints = [];
var pointbegain = null;
// set the amount of steps from node
var step = parseInt(360/nodes);
// the for loop creates a series of points that define the circle, counting by the amount of steps, by 9 in the case of 40 nodes
for(var i=0; i<=360; i+=step){
var point1 = new GLatLng(c_center.lat() + (radius / latConv * Math.cos(i * Math.PI / 180)),
c_center.lng() + (radius / lngConv * Math.sin(i * Math.PI / 180)));
if(i==0){
pointbegain= point1;
}
cpoints.push(point1);
}
//cpoints.push(pointbegain);
polygon = new GPolygon(cpoints, "#000000", 1, 1, "#8000000", 0.5);
//bigmap_rad.addOverlay(polygon);
(Here bigmap_rad is a google map v2 Map object and cpoint is passed to that event listener)
I am using this google map v2 code to turn it into v3 . But stumbled on this
var c_center = new GLatLng(cpoint.y,cpoint.x);
I cant find the alternative of this cpoint.y and cpoint.x for google map api v3. Please someone suggest me the solution. Thanks in advance.
I think I got a solution. Just used this
var c_center = new google.maps.LatLng(event.latLng.lat(),event.latLng.lng());
in place of
var c_center = new GLatLng(cpoint.y,cpoint.x);
I had a similar issue and solved it by replacing the x and y with lat() and lng().
That would make your code be.
var c_center = new google.maps.LatLng(cpoint.lat(),cpoint.lng());
Maybe that could have worked and is a quick workaround that helped me.
ok so you need to have an event listener on the map for clicks. And you want to find out the point that's been clicked.
google.maps.event.addListener(map, 'click', function(event) {
console.log(event.latLng);
var yourCoordinates = event.latLng;
});
See https://developers.google.com/maps/documentation/javascript/events#EventArguments

Box/Rectangle Draw Selection in Google Maps

I am working on Google Maps and want to implement a feature where a user can draw a box/rectangle using his/her mouse to select a region on map (like selecting multiple files in windows). Upon selection, I want to get all the markers that fall in the region. I have been looking around both Google Maps api and search but I am unable to find a solution. I tried using jQuery Selectable for selection but all it returns is a bunch of divs from which I am unable to determine if any marker is selected or not.
I found a Library keydragzoom (http://google-maps-utility-library-v3.googlecode.com/svn/tags/keydragzoom/1.0/docs/reference.html) and used it to draw a rectangle on the page.
Later, I edit the library and stopped it from zooming the selected area and instead made it return the correct co-ordinates in 'dragend' event. Then I manually looped through all the marker on the map to find the markers that are within that particular region. The library was not giving me the proper co-ordinates to I made the following changes.
Changed the DragZoom function to
var prj = null;
function DragZoom(map, opt_zoomOpts) {
var ov = new google.maps.OverlayView();
var me = this;
ov.onAdd = function () {
me.init_(map, opt_zoomOpts);
};
ov.draw = function () {
};
ov.onRemove = function () {
};
ov.setMap(map);
this.prjov_ = ov;
google.maps.event.addListener(map, 'idle', function () {
prj = ov.getProjection();
});
}
and DragZoom.prototype.onMouseUp_ function to
DragZoom.prototype.onMouseUp_ = function (e) {
this.mouseDown_ = false;
if (this.dragging_) {
var left = Math.min(this.startPt_.x, this.endPt_.x);
var top = Math.min(this.startPt_.y, this.endPt_.y);
var width = Math.abs(this.startPt_.x - this.endPt_.x);
var height = Math.abs(this.startPt_.y - this.endPt_.y);
var points={
top: top,
left: left,
bottom: top + height,
right: left + width
};
var prj = this.prjov_.getProjection();
// 2009-05-29: since V3 does not have fromContainerPixel,
//needs find offset here
var containerPos = getElementPosition(this.map_.getDiv());
var mapPanePos = getElementPosition(this.prjov_.getPanes().mapPane);
left = left + (containerPos.left - mapPanePos.left);
top = top + (containerPos.top - mapPanePos.top);
var sw = prj.fromDivPixelToLatLng(new google.maps.Point(left, top + height));
var ne = prj.fromDivPixelToLatLng(new google.maps.Point(left + width, top));
var bnds = new google.maps.LatLngBounds(sw, ne);
//this.map_.fitBounds(bnds);
this.dragging_ = false;
this.boxDiv_.style.display = 'none';
/**
* This event is fired when the drag operation ends.
* Note that the event is not fired if the hot key is released before the drag operation ends.
* #name DragZoom#dragend
* #param {GLatLngBounds} newBounds
* #event
*/
google.maps.event.trigger(this, 'dragend', points);
}
};

Google Maps API 3 overlay, need to get the projection

I'm trying to do an overlay on Google Maps. I have generated tiles of my image using maptiler, but the example generated by maptiler is in v2 and i want to use v3. The example generated by maptiler is also very complex and does some unnecessary opacity stuff. Now v3 of GM has changed a lot since v2 and i have some problems to generate the LatLng of a certain point on the screen. getProjection() keeps being undefined, whatever i do, any idea how to get the projection?
<script>
var mapBounds = new google.maps.LatLngBounds();
var mapMinZoom = 8;
var mapMaxZoom = 14;
var overlay;
var maptiler = new google.maps.ImageMapType({
getTileUrl: function(coord, zoom) {
if ((zoom < mapMinZoom) || (zoom > mapMaxZoom)) {
return "none.png";
}
var ymax = 1 << zoom;
var y = ymax - coord.y -1;
var tileBounds = new google.maps.LatLngBounds(
overlay.getProjection().fromDivPixelToLatLng( new google.maps.Point( (coord.x)*256, (coord.y+1)*256 ) , zoom ),
overlay.getProjection().fromDivPixelToLatLng( new google.maps.Point( (coord.x+1)*256, (coord.y)*256 ) , zoom )
);
if (mapBounds.intersects(tileBounds)) {
return "" + zoom + "/" + coord.x + "/" + (Math.pow(2,zoom)-coord.y-1) + ".png";
} else {
return "none.png";
}
},
tileSize: new google.maps.Size(256, 256),
isPng: true
});
var map;
function initialize() {
map = new google.maps.Map(document.getElementById("map_canvas"));
map.setZoom(11);
map.setMapTypeId('satellite');
mapBounds.extend(new google.maps.LatLng(50.9388615939, 3.80480816501));
mapBounds.extend(new google.maps.LatLng(51.4402541425, 4.73612507791));
map.fitBounds(mapBounds);
overlay = new google.maps.OverlayView();
overlay.draw = function() {};
overlay.setMap(map);
map.overlayMapTypes.insertAt(0, maptiler);
}
</script>
the overlay part is a hack i found on the internet which supposed to get you to the projection. unfortunately it didn't work. Any idea's how to fix this? in V2 you could do something like this:
var mercator = new GMercatorProjection()
mercator.fromPixelToLatLng( new GPoint( (tile.x)*256, (tile.y+1)*256 ) , zoom )
But this isn't possible anymore in v3.
Anyone that can help?
The example generated by maptiler can be found here:
http://gmapsexample.staging1.kunstmaan.be/googlemapsv2.html
This is a simple example in v3 which works:
http://gmapsexample.staging1.kunstmaan.be/googlemapsv3_simple.html
but i want everything except the map to be a specific color, so this is the example i'm trying to get working:
http://gmapsexample.staging1.kunstmaan.be/googlemapsv3.html
thanks,
Daan
You do not need to worry about getting or setting the projection if you are using MapTiler
These two YouTube videos will walk you through (among other things) using MapTiler with Google Maps API v3 map styled to be a certain color which is (if I understand correctly) exactly what you're asking about:
http://www.youtube.com/watch?v=CeSFUSZLeao
http://www.youtube.com/watch?v=WqSOLca2xOc
Try this as your Mercator function:
function GMercatorProjection() {
this.pixelOrigin_ = new google.maps.Point(tileSize / 2, tileSize / 2);
this.pixelsPerLonDegree_ = tileSize / 360;
this.pixelsPerLonRadian_ = tileSize / (2 * Math.PI)
}
Check the documentation about custom overlays in API3
https://code.google.com/apis/maps/documentation/javascript/overlays.html#CustomOverlays

Google Maps add 2 GEvent Listeners. 1 for each marker

I have the following code which lets the user plot two points on a Google MAP. I then want to be able to catch the event for each point(marker) being dragged to a new location. I am bad at Javascript and have spent hours trying to do this so I think it's time I get some help..
What I am trying to do is get the user to plot two points(markers) draggable on the map. I then want to be able to have my script write the positions(lat,long) to the document. I will then calculate the distance between these as part of a shipping app I am making.
I would like to have the contents of the document (lat,long) updated when a marker(point) is dragged to a new location.
Also, I fixed a schoolboy error where the point vars were being decalred inside the switch statement. My problem is fixed by moving the Add event listener statements inside the switch statement. Thanks Cannonade :)
The next thing now is to try and calculate the distance (crow flies) between the two points
Again, thanks for you help.. appreciated as always!!
Updated Code that works:
var map = null;
var geocoder = null;
var zoom = 15;
var first_point = false;
var boundary = new Array();
var cCount = 0;
var point1;
var point2;
function initialize() {
if (GBrowserIsCompatible()) {
first_point = false;
map = new GMap2(document.getElementById("map_canvas"));
var center = new GLatLng(37.4419, -122.1419);
map.setCenter(center, zoom);
GEvent.addListener(map, "click", function(overlay,point)
{
if (overlay != null)
{}
else
{
var n = boundary.length;
switch (cCount)
{
case 0:
point1 = new GMarker(point,{draggable: true});
map.addOverlay(point1);
cCount++;
GEvent.addListener(point1, "dragend", function()
{
alert('P1 Dragged');
});
break;
case 1:
point2 = new GMarker(point,{draggable: true});
map.addOverlay(point2);
cCount++;
GEvent.addListener(point2, "dragend", function()
{
alert('P2 Dragged');
});
break;
case 2:
map.clearOverlays();
cCount=0;
break;
}
}
});
map.addControl(new GSmallMapControl());
geocoder = new GClientGeocoder();
}
}
I have taken your code and made the following fixes:
Fixed the unbalanced brackets I mentioned in the comment.
Moved the two addListener calls into the switch statement so that the point1 and point2 variables are still in scope when you attach the events.
You can check out the example here (source).
Edit:
Here is some Javascript code to get the linear distance between two points (in meters):
/* Convert degress to radians */
function deg2rad(deg) {
return deg / (180 / Math.PI);
}
/* Calculate distance between two points */
function point_distance(a, b) {
var r = 6378700;
var lat1 = a.y;
var lat2 = b.y;
var lon1 = a.x;
var lon2 = b.x;
var dist = r * Math.acos(Math.sin(deg2rad(lat1)) * Math.sin(deg2rad(lat2)) +
Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
Math.cos(deg2rad(lon1 - lon2)));
return dist;
}
This is based on the approximate radius of the earth being 6378700m.

Google Maps - zoom to fit markers doesn't work when map isn't visible

I'm using the Google Maps API v2. I add markers to the map and then zoom to fit those markers. This works fine if the map is visible I do this. But if not - for example, if I have a tabstrip, and the map's tab isn't selected when the page loads - then when I do show the map, the zoom level and center are wrong.
Here's a simple test case (uses jQuery):
<script type="text/javascript">
var scale = Math.random() * 20;
$(document).ready(function() {
var $container = $('#container');
// $container.hide();
var map = new GMap2($('#map')[0]);
$container.show();
var markerBounds = new GLatLngBounds();
for (var i = 0; i < 10; i++) {
var randomPoint = new GLatLng(38.935394 + (Math.random() - 0.5) * scale, -77.061382 + (Math.random() - 0.5) * scale);
map.addOverlay(new GMarker(randomPoint));
markerBounds.extend(randomPoint);
}
map.setCenter(markerBounds.getCenter(), map.getBoundsZoomLevel(markerBounds));
});
</script>
<div id="container">
<div id="map" style="margin: 100px; width: 450px; height: 300px;"></div>
</div>
This works fine as is, but if you uncomment $container.hide() it's all whacked out.
Is there a way to get the Google Maps API to work properly on a div that's not visible?
Here's what I've ended up doing, for what it's worth.
$(".TabPanel").watch("display,visibility", function() {
$(".MapContainer", this).each(function() {
if ($(this).is(":visible") == true) {
$(this).zoomToFitMarkers();
};
});
});
This uses Rick Strahl's monitoring plugin for jQuery to watch the tab panel for visibility changes, and then reapplies the zoom logic.
For completeness here's my zoomToFitMarkers extension:
$.fn.zoomToFitMarkers = function() {
var map = this[0];
map.gmap.checkResize();
map.bounds = new GLatLngBounds();
if (!!map.gmap.getOverlays) {
for (i = 0; i < map.gmap.getOverlays.length; i++) {
map.bounds.extend(map.gmap.getOverlays[i].getLatLng());
}
if (map.bounds && !map.bounds.isEmpty()) {
var zoomLevel = map.gmap.getBoundsZoomLevel(map.bounds);
zoomLevel = zoomLevel > 9 ? 9 : zoomLevel;
zoomLevel = zoomLevel < 2 ? 2 : zoomLevel;
map.gmap.setCenter(map.bounds.getCenter(), zoomLevel);
}
}
map.gmap.checkResize();
};
This relies on a couple of conventions:
The GMap2 object is stored in map.gmap, where map is the target DOM element:
var map= $("div#MapTarget")[0];
map.gmap = new google.maps.Map2(map);
Each time a marker is added to the map, it's stored in an array for future use:
var marker = new GMarker(point);
map.gmap.addOverlay(marker);
// Keep track of new marker in getOverlays array
if (!map.gmap.getOverlays) map.gmap.getOverlays = new Array();
map.gmap.getOverlays.push(marker);
All you would need to do is to create the GMaps2() before anything else. You can then hide() the container, add the points, get the getBoundsZoomLevel(), show() it again, and it should work fine.
Try the following:
$(document).ready(function() {
var $container = $('#container');
// First create the Map.
var map = new GMap2($('#map')[0]);
// The container can be hidden immediately afterwards.
$container.hide();
// Now you can do whatever you like!
var markerBounds = new GLatLngBounds();
for (var i = 0; i < 10; i++) {
var randomPoint = new GLatLng( 38.935394 + (Math.random() - 0.5) * scale,
-77.061382 + (Math.random() - 0.5) * scale);
map.addOverlay(new GMarker(randomPoint));
markerBounds.extend(randomPoint);
}
map.setCenter(markerBounds.getCenter(), map.getBoundsZoomLevel(markerBounds));
// Finally unhide the container.
$container.show();
});