I'm back with more work on the Google Map that I've been working on. Here is the situation:
I have a map of Virginia. It will have markers in it, but I need the markers to be added/removed/editable by several people. As a result, I created a Google map in "My Places" and I'm importing the resulting KML file into a map I'm loading into the API.
I'm also trying to "white out" neighboring states by drawing a polygon over them and adding a white layer over them with 75% opacity, in order to make the state of Virginia stand out more.
However, I've run into a problem where if a marker from the KML layer "overlaps" onto the polygon covering a bordering state, the opaque polygon covers the layer AND the marker becomes unclickable. If one zooms in enough, one can click the marker, but I want people to be able to click the marker from the original zoom.
I've tried making the markers first, then adding the KML, and doing the KML first then drawing the polygons, but it doesn't seem to matter. I even tried a variation of the solution here: Handle when drawing of polygons is complete in google maps api v3 where I put the trigger to add the KML layer inside of the listener event, but still no dice.
My searching on Google also hasn't led me to anything that looks useful. I don't know if this is still a problem with the order the layers are being ordered, or if polygons somehow "override" a KML layer, regardless of the order, or if there is some way to explicitly tell the KML markers to stay on top of the polygons.
First off, here is the main code I'm using to draw the layers right now:
function initialize() {
var mapOptions = {
zoom: 7,
center: new google.maps.LatLng(38, -79.5),
disableDefaultUI: true,
mapTypeId: google.maps.MapTypeId.TERRAIN
}
var map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
borderingStates(map);
var participantsLayer = new google.maps.KmlLayer('https://maps.google.com/maps/ms?msa=0&msid=204048902337864904598.0004cc332e8034251c1db&ie=UTF8&ll=37.668046,-80.289717&spn=1.959603,5.642338&output=kml',{preserveViewport:true});
google.maps.event.addListener(map,'idle', function() {
participantsLayer.setMap(map);
});
}
function loadScript() {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "https://maps.googleapis.com/maps/api/js?key=abc.def&sensor=false&callback=initialize";
document.body.appendChild(script);
}
window.onload = loadScript;
Next, here is an example of the code I'm using to draw the polygons. This happens when I call the borderingStates function above:
//Delaware
DEpoints = [
new google.maps.LatLng(39.7188, -75.7919),
new google.maps.LatLng(39.5210, -75.7837),
...
new google.maps.LatLng(39.8296, -75.6477),
new google.maps.LatLng(39.7199, -75.7906)
];
// Construct the polygon
var Delaware = new google.maps.Polygon({
paths: DEpoints,
fillColor: invisColor,
strokeOpacity: 0,
fillOpacity: .75
});
Delaware.setMap(map);
And then I repeat that for each state I draw a polygon for - I think there are 6 or 7.
I can't tell that there is anything in particular about the code that wouldn't cause it to work other than perhaps how Google Maps inherently treats polygons and KML Layers.
I would suggest making your polygons with KmlLayer (or FusionTablesLayer) as well.
You can control the ordering of layers by the order that you add them to the map (first one is on the bottom). I believe native Google Maps API v3 Polygons will always appear above layers. Your other option would be to make the Polygons "unclickable" (clickable: false) [not sure if this will work or not].
This is my first submission and at the risk of being labelled a cowboy, this was my solution to this problem:
Remove the Polygon on the first click event
Set a timer to reinstate the Polygon (after 1 1/2 seconds)
Capture the click event on the KML element during the 1 1/2 second window of opportunity
google.maps.event.addListener(polygon, 'click', function () {
polygon.setMap(null); // hide polygon for a 1 1/2 seconds
window.setTimeout(function () {
polygon.setMap(map_canvas);
}, 1500);
// process polygon click now
});
This method does mean that you'll process both the Polygon and KML element click events but in our case that was fine.
Related
I have a map that has markers programmatically placed on it based on values in my database. When you click on a marker, it will go to a detail page for that location.
My problem is that when the map is sufficiently zoomed out, markers that are close enough to each other appear as a single marker, in effect hiding some of the markers. Is there a way to tell programmatically whether a marker is part of a group of markers or is hidden/covered up by other markers?
My intention is to do something like this for each dynamically generated marker:
marker.addListener('click', function() {
// if marker is not hiding any other markers
window.location.href = markerURL;
// else if it is hiding markers/is part of a group of markers
map.setZoom(15);
map.setCenter(marker.getPosition());
});
I have checked the Marker API documentation, but can't seem to find any useful methods. getClickable and getVisible always return true in my case, regardless of whether a marker is covered by another marker. Any suggestions? Thank you!
I ended up going with MarkerClusterer to solve my problem. I was hoping for a simpler solution, but this turned out to be pretty simple after all.
The only thing I needed to add to my existing marker-generating code was a list: var markers = [];, and then I called markers.push(marker); on all of my markers. The final step was to create a new MarkerClusterer object:
var markerCluster = new MarkerClusterer(map, markers, options);
And MarkerClusterer handles the rest more or less (the options parameter is optional, but I used it to set the path to my images and set the maximum zoom level). Now, in the situations where previously my markers were stacked on top of each other, making it impossible to see or click certain markers at certain zoom levels, I instead see a cluster with a number indicating the number of markers in that cluster. Clicking the cluster icon will further zoom in, revealing my markers.
All of this was done following the simple usage example on their github page, but they have pretty good documentation too. Most of my time getting this to work right was actually spent styling the cluster icons to match my site's color scheme...
Working on this application which I have broken down here.
http://jsfiddle.net/pPMqQ/81/
In this example I want to
show markers only inside the shape area
allow for zoom of the map and scaling of the shape area
here is my pseudo code
identifyMarkersInShape: function(){
//__ function is invoked every time a shape is drawn/editted
// hides all the markers
//finds the markers inside the given shape
},
bindEvents: function(){
//handle zoom of the map and scale of the path shape
}
Fully working example: http://jsfiddle.net/PDf9G/5/
Edit: Now simplifies polygon before adding it to the map. Editing the polygon works as well.
First, the css and html: I moved the div called #canvas1 after the map and gave it absolute positioning and z-index = 0. I also gave the map the same absolute positioning to ensure that they always line up with each other, and gave it a z-index of 10.
When the draw button is clicked the canvas is moved to the front. The user can use it to draw free form using d3. When they are done the shape they drew is converted to a google maps polygon.
addShapeToBaseMap: function(divCoords) {
var geoCoords = []
for (var i = 0; i < divCoords.length; i++)
geoCoords.push(overlay.getProjection().fromContainerPixelToLatLng(new google.maps.Point(Number(divCoords[i][0]), Number(divCoords[i][1]))));
poly = new google.maps.Polygon({
paths: geoCoords,
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: '#FF0000',
fillOpacity: 0.35
});
poly.setMap(map);
Then we do do the hiding/showing of the markers. Using d3 this way is really silly and defeats the purpose. You need to add the google geometry library to your url to use this (&libraries=geometry). I'm sure there are faster ways and if you're dealing with large datasets you'll want to make this better.
for (var i = 0; i < data.length; i++) {
if (!google.maps.geometry.poly.containsLocation(new google.maps.LatLng(data[i]['lat'], data[i]['lng']), poly)) {
d3.select("#" + data[i]['name']).classed({'hide': true});
} else {
d3.select("#" + data[i]['name']).classed({'hide': false});
}
}
This works because when we appended the markers we added their name as the id on the marker element. The only reason I can see to do this is because the svg gives you better control over styling. Last:
svg.select(".selection").remove()
d3.select("#canvas1").classed({'front': false});
$('.draw').removeClass('highlight');
},
We remove the shape we drew from the drawing layer. If we don't do this, if the user moves the map and then turns the drawing layer back on, the shape will be in the wrong place. Then we move the canvas to the back and turn off the highlighting on the drawing button.
The edit function was taken from your most recent code. If the edit button or polygon is clicked the editing function is turned on on the polygon.
I would also recommend taking a look at Leaflet. The integration with d3 is a bit easier and you can have multiple svg layers, which would allow you to put the drawing layer as a map overlay instead of a separate div.
geojson-utils is a node/browser javascript package that has a bunch of utilities for dealing with geojson paths.
One of the many things that it has is a very solid point in polygon algorithm designed for dealing with geojson paths.
You also might want to consider using Leaflet.js instead of Google Maps, it has a few more tools for going to and from geojson based data. It also has a lot of really useful tools already written like Leaflet.draw which has the code already needed to "draw" these paths on top of the maps.
I'm trying to make sure that my maps center point is with in the boundaries of a kml layer, I've not been able to find much about this but I modified some code from here which seems like it could work, but of course doesn't.
From the Google Maps API I can't tell whether or not .contains() is meant to work with kml layers, or if there is a similar method. Any ideas?
// bounds of the desired area
var kmlLayer = new google.maps.KmlLayer(kmlLayerURL, {map:map, suppressInfoWindows: true, preserveViewport:true});
google.maps.event.addListener(map, 'center_changed', function() {
if (kmlLayer.contains(map.getCenter())) {
alert("withnin kml layer bounds");
}
});
The KmlLayer has a method getDefaultViewport which will give you the default viewport for the overlay (the bounds the map will be centered on to display all the contents.
getDefaultViewport() LatLngBounds Get the default viewport for the layer being displayed.
The returned LatLngBounds has a contains method.
You can't access any of the internal content of the KmlLayer.
The Google Maps API for a Polygon does not offer a drag method.
What would be an efficient way of implementing such a feature (i.e., sufficiently optimised so that it would not kill a four year old laptop)?
Thank you!
I found the Google Maps V2 Polygon Implementation to be very limiting for the needs I have had and solved it by creating a custom overlay. My group is currently stuck on IE6 so I have yet to migrate over to Google Maps V3 - but taking a quick look at the API shows that you could probably do a similar thing that I did in V2 with V3.
Essentially the idea is:
Create a Custom Overlay
Populate it with your own SVG/VML Polygons and attach a drag event to this custom polygon object
Custom Overlays:
Here is some information to get you started on making your own custom overlay:
http://code.google.com/apis/maps/documentation/javascript/overlays.html#CustomOverlays
Creating your own "Dragable" Polygon Object:
Once you get that down you'll want to add your own polygons to the custom overlay instead of using GPolygons. I went through the painful process of learning SVG/VML and writing a library to bridge SVG/VML together - you could do that, but I would recommend starting by trying to use another library such as Raphaël.
http://raphaeljs.com/
Using Raphaël will save you a whole lot of time trying to figure out how to get cross-browser Vector Graphic (Polygon) functionality - and best of all it supports drag events already, here is an example from their library:
http://raphaeljs.com/graffle.html
Once you have a custom overlay and you are able to throw some Raphaël objects onto it the last step is to translate the coordinates you want from a Lat/Lng value to a Pixel value. This is available in the MapCanvasProjection of V3:
http://code.google.com/apis/maps/documentation/javascript/reference.html#MapCanvasProjection
You can use fromLatLngToDivPixel to figure out what the actual pixel values are for the points on your Raphael polygon, draw it, then add it to the overlay with a drag event.
Since version 3.11 (dated Jan 22, 2013) it's possible to just set the draggable property onto the google.maps.Polygon instance; see this example.
If you want to programmatically move a polygon, you'll need a custom Google Maps Extension which I wrote, as the API does not provide such a method.
Here's how I do it. Find the approximate center of the polygon, and add a marker, then add a drag listener to the marker. On lat/lng change, subtract the difference from the original marker lat/lng, subtract the difference to each of the paths, then, set the original position to the new position. Make sure that in your javascript api call that you have library=geometry,drawing
google.maps.event.addListener(draw, 'overlaycomplete', function(shape) {
// POLYGON
if (shape.type == 'polygon') {
var bounds = new google.maps.LatLngBounds(); var i;
var path = shape.overlay.getPath();
for (i = 0; i < path.length; i++) { bounds.extend(path.getAt(i)); }
shape.latLng = bounds.getCenter();
marker = getMarker(map,shape);
shape.overlay.marker = marker;
markers.push(marker);
}
google.maps.event.addListener(marker, 'drag', function(event) {
shape.overlay.move(event.latLng, shape, path);
});
google.maps.event.addListener(shape.overlay, 'rightclick', function() {
this.setMap(null);
this.marker.setMap(null);
draw.setDrawingMode('polygon');
});
});
}
google.maps.Polygon.prototype.move = function(latLng, shape, p) {
var lat = latLng.lat();
var lng = latLng.lng();
latDiff = shape.latLng.lat()-lat;
lngDiff = shape.latLng.lng()-lng;
for (i = 0; i < p.length; i++) {
pLat = p.getAt(i).lat();
pLng = p.getAt(i).lng();
p.setAt(i,new google.maps.LatLng(pLat-latDiff,pLng-lngDiff));
}
shape.latLng = latLng;
}
function getMarker(map,shape){
var infowindow = new google.maps.InfoWindow();
if(shape.type=='polygon'){ latLng = shape.latLng; }
marker = new google.maps.Marker({
position: latLng,
map:map,
draggable:true,
clickable: true,
animation: google.maps.Animation.DROP
});
shape.overlay.marker = marker;
shape.overlay.bindTo('center',marker,'position');
google.maps.event.addListener(marker, 'click', (function(marker) {
return function() {
infowindow.setContent('polygon');
infowindow.open(map, marker);
toggleBounce(marker);
}
})(marker));
google.maps.event.addListener(infowindow,'closeclick', (function(marker) {
return function() {
marker.setAnimation(null);
}
})(marker));
return marker;
}
If you have any questions, feel free to contact me.
You could have markers for each point on the polygon, these markers could have drag and at the end of each drag, the polygon could be redrawn.
You could also have a marker in the center of the polygon representing the polygon as a whole, when you move that marker, every marker could be moved by the same amount to maintain the shape.
Okay - so after seeing the website you are trying to implement I started to feel like Raphael may not be necessary because it is a pretty heavy JS Library - and if you are only trying to draw a rectangle polygon I thought, why not just do it with a single lightweight DIV instead?
However I think the Raphael solution would still hold water for many other cases - so I think I'll just post another possible answer.
Here is a working example I threw together:
http://www.johnmick.net/drag-div-v3/
Feel free to take a look at the source:
http://www.johnmick.net/drag-div-v3/js/main.js
Essentially we do the following
Create the Custom Overlay
Create the draggable div polygon and, using jQuery UI, make it draggable
Tie an event that listens to when the dragging has stopped that updates the LatLng position of the rectangle
Add the object to the Custom Overlay
Implement the draw function to redraw the rectangle during zooms and pans
Currently I am only storing one LatLng value for the Rectangle (being the top left corner) - you could easily extend this example to store all 4 points of the rectangle and have the shape dynamically resize itself on zooms. You may want to do that, otherwise as users zoom out they will get a climate report for a larger and larger area.
I really like how Trulia.com has created their custom Google Map InfoWindows.
What I like in particular about Trulia's implementation of the InfoWindow is:
Extends beyond the map border: Google Maps InfoWindows are contained within the map border whereas Trulia's seems to be able to float on-top of the map
Always displays InfoWindow near map Center: Google Maps InfoWindows always display the InfoWindow above the marker whereas Trulia InfoWindows always display the InfoWindow as close the center of the map as possible. For example, if the map marker icon is on the top of the map near the border, the Trulia InfoWindow is displayed below the map marker icon
InfoWindow is displayed on mouse hover (not 'click'): With the default Google Maps InfoWindow, you have to 'click' the map marker icon to display the InfoWindow whereas Trulia InfoWindows are display simply by hovering over the map marker icon.
I found the PdMarker, which is a 3rd party extension for Google Map InfoWindows that accomplishes most of the above bullets but not all. 1) It does not extend beyond the map border, 2) it does not work with Google Maps API version 3 (only version 2).
Anyone know how Trulia is accomplishing their InfoWindow-like implementation on Google Maps API v3?
That's an interesting question. I've been playing with maps recently too. I'm far from expert but I can tell you what I know.
I think the site you mentioned uses a custom div overlay rather than google's info window.
1. InfoWindow is displayed on mouse hover (not 'click')
This can be done with event listeners. For example in maps API v3:
google.maps.event.addListener(marker, 'mouseover', function() {
// myDiv.style.visibility = 'visible'
});
google.maps.event.addListener(marker, 'mouseout', function() {
// myDiv.style.visibility = 'hidden'
});
Here's is a pretty good example of how this can be done.
2. Extends beyond the map border
3. Always displays InfoWindow near map Center
Both of these can be achieved using CSS: (2) using z-index and (3) with position.
There is a similar example of using custom marker tooltips which you can find here. It also shows how you can utilize mouseovers to pop-up tooltips by hovering other elements on your page.
myElement.onmouseover = function(){
google.maps.event.trigger(marker, 'mouseover');
}
myElement.onmouseout = function(){
google.maps.event.trigger(marker, 'mouseout');
}
Finally, another site that makes a good use of maps, although this one uses V2 of the API. Hope this helps.
http://google-maps-utility-library-v3.googlecode.com/svn/trunk/infobox/docs/examples.html
is an extension to google maps which allows you to create custom infowindows