I've embedded a Google Map into my application. Interacting with the map works. Responding to map events works (map zoom_changed, marker dragend, etc). However, only some parts of the map are visible (the marker and the Google logo for example), but the maps themselves are not (at least they don't appear 95% of the time).
Can anyone tell me what's going on here?
EDIT: I'm using this as a KnockoutJS component (inserted with with <gui-map></gui-map>). The source code below. I don't believe the use of KnockoutJS has anything to do with the map issues because: a) all the observables are wired up correctly and working 100% of the time; and b) the map does work randomly without any code changes 5% of the time.
define(['knockout', 'underscore'], function(ko, _){
function Map(params, componentInfo) {
var self = this;
var defaultPosition = {lat:-25,lng:-130};
var width = ko.isObservable(params.width) ? params.width : ko.observable(params.width ? params.width : '100px');
var height = ko.isObservable(params.height) ? params.height : ko.observable(params.height ? params.height : '100px');
var center = ko.isObservable(params.center) ? params.center : ko.observable(params.center ? params.center : defaultPosition);
var zoom = ko.isObservable(params.zoom) ? params.zoom : ko.observable(params.zoom ? params.zoom : 12);
var marker = ko.isObservable(params.marker) ? params.marker : ko.observable(params.marker ? params.marker : defaultPosition);
var element = componentInfo.element;
element.style.display = 'block';
element.style.width = width();
element.style.height = height();
width.subscribe(function(){
element.style.width = width();
});
height.subscribe(function(){
element.style.height = height();
});
function onObservableCenterChanged(newValue){
onObservableCenterChanged.changing = 1;
console.log('updating center map');
map.setCenter(newValue);
setTimeout(function(){
onObservableCenterChanged.changing = 0;
}, 500);
}
center.subscribe(onObservableCenterChanged);
function onObservableZoomChanged(newValue){
onObservableZoomChanged.changing = 1;
console.log('updating map zoom');
map.setZoom(newValue);
setTimeout(function(){
onObservableZoomChanged.changing = 0;
}, 500);
}
zoom.subscribe(onObservableZoomChanged);
var map = new google.maps.Map(element, {
center: center(),
zoom: zoom()
});
var mapMarker = new google.maps.Marker({
position:center(),
map:map,
title:'',
draggable:true
});
map.addListener('center_changed', (function(){
var mapCenterChangeTimeout;
return function(){
if (mapCenterChangeTimeout) {
clearTimeout(mapCenterChangeTimeout);
}
mapCenterChangeTimeout = setTimeout(function(){
if (!onObservableCenterChanged.changing) {
var newCenter = map.getCenter();
console.log('updating center observble');
center({
lat:newCenter.lat(),
lng:newCenter.lng()
});
}
}, 500);
};
})());
map.addListener('zoom_changed', (function(){
var mapZoomChangedTimeout;
return function(){
if (mapZoomChangedTimeout) {
clearTimeout(mapZoomChangedTimeout);
}
mapZoomChangedTimeout = setTimeout(function(){
if (!onObservableZoomChanged.changing) {
console.log('updating zoom observable');
zoom(map.getZoom());
}
}, 500);
};
})());
mapMarker.addListener('dragend', function(){
var newPosition = mapMarker.getPosition();
marker({
lat:newPosition.lat(),
lng:newPosition.lng()
});
});
}
ko.components.register('gui-map', {
template:{
require:'text!components/gui/map.html'
},
viewModel:{
createViewModel:function(params, componentInfo){
return new Map(params, componentInfo);
}
}
});
});
EDIT2: I have succesfully got the above to work by wrapping the entire body of the Map function (with exception of the var self = this; assignment) in a anonymous function called with setTimeout() (with a delay of 5000 ms). However, all the code executes after the DOMContentLoaded event anyway, so I'm not sure why this is an issue.
The cause of the problem was that the Google Maps Javascript API does NOT render the map correctly if the element in which the map is contained is not visible. In my situation, the map was located in a hidden tab. The timeout mentioned above solved the problem because the delay gave me long enough to switch tabs before the function that calls the Google Maps API could be executed (if I delayed opening the tab until after the Google Maps API was called, the issue would re-surface).
I got around this issue by manually triggering a resize on the map, and manually updating the various controls on the map, e.g.:
google.maps.event.trigger(map, 'resize');
infoWindow.setContent(infoWindowElement);
infoWindow.close();
infoWindow.open(map, marker);
I hope this solution helps someone else.
Related
I am relative new to Angularjs and i am currently working on a project but I hit an obstacle. I've been trying multiple ways to do this but Im all out of idea. Here is a picture of what I want to do:
Map
So on this picture, there are icons and what I want to do is upon clicking an icon, a pop-up window will appear. If you look at the shaded area on top right, that is how I want my the window to be and look like as I already built a template for it and right now the template is static div being displayed.
Here is my code so far:
$scope.cameraData = [];
$scope.markers = [];
$http.get($scope.docRoot + 'public/data/streetcams.json')
.success(function(data) {
$scope.cameraData = data;
for(var marker in $scope.cameraData) {
if($scope.cameraData.hasOwnProperty(marker)) {
$scope.cameraData[marker].icon = cameraIcon;
$scope.cameraData[marker].layer = 'video';
}
}
$scope.markers = $scope.cameraData;
}).error(function(data) {
console.log("Unable to load streecam data file");
});
$scope.windowOption = {
show:false
};
var x, y;
$scope.$on("leafletDirectiveMarker.map.click", function(event, args) {
console.log("this is where the marker is");
console.log (args.latlng.lat, args.latlng.lng);
for ( var marker in $scope.markers) {
x = args.latlng.lat;
y = args.latlng.lng;
if (marker.lat === x && marker.lng === y ) {
$scope.windowOption.show = !$scope.windowOption.show;
}
}*/
});
$scope.closePopupOnClick = function () {
$scope.windowOption.show = false;
};
So $scope.markers contains the information of all icons (title, lat and longitude) and my idea is to retrieve the latitude and longitude of each click event and compare that with latitude and longitude of each marker in the markers array, if they are equal then show to popup window. But it's not working and Im not sure what else to do. Any tips will help!
Thanks!
So im using this function to print my Google Maps API V3:
function printit(){
var content = window.document.getElementById("map-canvas");
var newWindow = window.open(); // open a new window
newWindow.document.write(content.innerHTML); // write the map into the new window
newWindow.print(); // print the new window
};
And this is the HTML button that im using:
<button onClick="printit()">Print</button>
The problem is that map-canvas is set to 100% width with CSS, so wen the map is printed only left side is visible (as much as it fits on A4 paper). I tryed adding this:
document.getElementById("map-canvas").style.width = "900px";
But i get an error. Can you help me with this?
The Google Maps API normally uses the ID map_canvas for the height and width properties instead of map-canvas. So it has to be:
document.getElementById("map_canvas").style.width = "900px";
Try
google.maps.event.trigger(map, 'resize');
I found the solution, it was not as simple as i thought it would be, here is the complete working code, it was tested in Chrome, Firefox, Opera and it works perfectly:
<script type="application/javascript">
function printit(){
var newWidth = 700,
content = document.getElementsByClassName("gm-style")[0],
translateX = (content.clientWidth - newWidth) / 2,
orgTranslate = content.firstChild.firstChild.style.transform,
left = content.firstChild.firstChild.style.left;
var win = window.open();
win.document.write(content.outerHTML);
var c = win.document.getElementsByClassName("gm-style")[0];
c.style.width = newWidth + "px";
if (orgTranslate) {
orgTranslate = orgTranslate.split(",");
orgTranslate[4]-= translateX;
c.firstChild.firstChild.style.transform = orgTranslate.join(",");
} else {
c.firstChild.firstChild.style.left = (parseInt(left, 10) - translateX) + "px"; // firefox uses absolute positioning
}
win.print();
};
var center;
function calculateCenter() {
center = map.getCenter();
}
google.maps.event.addDomListener(map, 'idle', function() {
calculateCenter();
});
google.maps.event.addDomListener(window, 'resize', function() {
map.setCenter(center);
});
</script>
And also the print button:
<button onClick="printit()">Print</button>
Hope this will help someone.
I have a single page application, using reactjs and react-router. I developed a maps component, and of course with google maps have a memory leak going from screen to screen if I try and cleanup the map on component did mount and rebuild it on component did mount. so I tried this approach:
What is the proper way to destroy a map instance
and re-using the map instance and the node combined with excessive measures to clean things up works well and stabilizes things. For cleanup, I
call clearInstanceListeners on every marker, map, window and document
set map to null on any markers
delete this.map on the component then set it to null
delete this.markers then set it to empty array
The issue i'm having at this point is that when I trigger resize, it's not resizing. after reading several other posts, and making sure i'm triggering after the div is shown, it doesn't resize. However, I have a resize function bound to window.onresize, and have noticed that after resizing the window, and the second time the function is called, the map does resize and center. here's that function:
sizeMapToContainer: function () {
google.maps.event.trigger(this.map, 'resize');
if (this.props.fitBounds) {
this.zoomInOnMarkers();
} else {
this.map.setCenter(new google.maps.LatLng(this.props.center[1], this.props.center[0]));
this.map.setZoom(this.props.zoom);
}
},
and the function for window resize:
window.onresize = function onResize () {
if (this.map) {
this.sizeMapToContainer();
}
}.bind(this);
My render call for react just returns a div, where later on in did mount I append the map div myself.
buildMap: function (nextProps) {
var mapProps;
if (nextProps) {
mapProps = nextProps;
} else {
mapProps = this.props;
}
var centerpoint = mapProps.center;
var zoom = mapProps.zoom || 8;
var center = new google.maps.LatLng(centerpoint[1], centerpoint[0]);
var mapNode = this.refs.map.getDOMNode();
var mapOptions = {
center: center,
zoom: zoom
};
if (mapInstance.map) {
window.tmMap = this.map = mapInstance.map;
//check if the nodes are already there
if (!mapNode.hasChildNodes()) {
mapNode.appendChild(mapInstance.node);
}
this.map.setOptions(mapOptions);
} else {
mapInstance.node = document.createElement('div');
mapInstance.node.classList.add('full-height');
mapInstance.map = new google.maps.Map(mapInstance.node, mapOptions);
window.tmMap = this.map = mapInstance.map;
mapNode.appendChild(mapInstance.node);
this.map.setOptions(mapOptions);
}
this.buildMarkers();
this.setMarkers();
this.bindMarkers();
google.maps.event.addListener(this.map, 'idle', this.onIdle);
},
This component is a nested component.
Please let me know if there is any other code that will help. I am of course using the latest google maps api, using it directly.
I am using the Geolocation Marker Script from the Google Maps Utilities Library V3 in order to display the position of a user.
What I want to achieve (I am a newbie to the Google Maps API!) is:
have the users current coordinates displayed (e.g. in a simple CSS container somewhere on the page)
connect an event to a marker. I should be triggered when the user is close.
Appreciate your help!
To display coordinates to the user, you would need a reference to a DOM Element. Then it's a simple matter of updating the content.
HTML On the Page
<div id="UserCoordinates">Waiting on GPS Position ...</div>
In Script
google.maps.event.addListener(GeoMarker, 'position_changed', function() {
var UserPosition = this.getPosition();
var DisplayElement = document.getElementById('UserCoordinates');
if(UserPosition === null) {
DisplayElement.innerHTML = 'Waiting on GPS Position...';
} else {
DisplayElement.innerHTML =
'Current Position: ' + UserPosition.toUrlValue();
}
});
This will show the user their current position as it changes. If you are going to continue using a full screen map, you'll probably want to implement the UserCoordinates div as a map control. The API Reference has a good overview and multiple examples on this.
Display an info window when the user is within X meters of a location
This is a little tricky because there are multiple scenarios to handle and you don't want the infowindow opening repeatedly as they move within your radius.
Distance calculation
I see you have a distance function in your code, but I recommend using the one in the Spherical Geometry library of the API. You just have to specifically load the library with your api script tag:
<script type="text/javascript"
src="https://maps.googleapis.com/maps/api/js?libraries=geometry&sensor=true_or_false">
</script>
Then you need to add to the position_changed event handler:
var IsWithinRadius = false; //assume they start outside of the circle
var RadiusInMeters = 1000; //within 1 km
var LocationOfInterest = map.getCenter();
google.maps.event.addListener(GeoMarker, 'position_changed', function() {
var UserPosition = this.getPosition();
var DisplayElement = document.getElementById('UserCoordinates');
if(UserPosition === null) {
DisplayElement.innerHTML = 'Waiting on GPS Position...';
IsWithinRadius = false; //you don't know where they are
} else {
DisplayElement.innerHTML =
'Current Position: ' + UserPosition.toUrlValue();
var IsCurrentPositionInRadius =
Math.abs(google.maps.geometry.spherical.computeDistanceBetween(
UserPosition, LocationOfInterest)) <= RadiusInMeters;
var JustEnteredRadius = !IsWithinRadius && IsCurrentPositionInRadius;
IsWithinRadius = IsCurrentPositionInRadius;
if(JustEnteredRadius) {
//trigger action here.
alert("Within raidus");
}
}
});
I'm trying to add custom controls to a Google map using the API. I already have two custom controls added and they work just fine. I tried to copy and paste the code for a third control (changing the relevant variables of course) and I keep getting the above error (in the title).
Chrome console and Firebug don't seem to point to a particular problem (it breaks inside the google maps api code). By progressively commented out lines, I've narrowed it down to this particular line:
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(churchControlDiv);
The full code for adding the control is as follows:
function ChurchControl(churchControlDiv, map) {
churchControlDiv.style.padding = '5px 0px';
var churchControlUI = document.createElement('DIV');
churchControlUI.style.backgroundColor = 'white';
churchControlUI.style.borderStyle = 'solid';
churchControlUI.style.borderWidth = '1px';
churchControlUI.style.borderColor = 'gray';
churchControlUI.style.boxShadow = 'rgba(0, 0, 0, 0.398438) 0px 2px 4px';
churchControlUI.style.cursor = 'pointer';
churchControlUI.style.textAlign = 'center';
churchControlUI.title = 'Click to see Churches';
churchControlDiv.appendChild(churchControlUI);
var churchControlText = document.createElement('DIV');
churchControlText.style.fontFamily = 'Arial,sans-serif';
churchControlText.style.fontSize = '13px';
churchControlText.style.padding = '1px 6px';
churchControlText.style.fontWeight = 'bold';
churchControlText.innerHTML = 'Churches<br>แสดงจำนวนคริสเตียน';
churchControlUI.appendChild(churchControlText);
google.maps.event.addDomListener(churchControlUI, 'click', function() {
toggle(churches);
if (churchControlText.style.fontWeight == 'bold') {
churchControlText.style.fontWeight = 'normal';
} else {
churchControlText.style.fontWeight = 'bold';
}
});
google.maps.event.addDomListener(churchControlUI, 'mouseover', function() {
churchControlUI.style.backgroundColor = '#e8e8e8';
});
google.maps.event.addDomListener(churchControlUI, 'mouseout', function() {
churchControlUI.style.backgroundColor = 'white';
});
}
function initialize(){
map = new google.maps.Map(document.getElementById("map_canvas"), {
center: centerLatLng,
zoom: 7,
streetViewControl: false,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
var churchControlDiv = document.createElement('DIV');
var churchControlDiv = new ChurchControl(churchControlDiv, map);
churchControlDiv.index = 3;
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(churchControlDiv);
}
Any ideas? Any reason why having 3 controls would be a problem?
I had the same error pop up on my console whilst following the tutorial for a different reason.
Rather than using default javascript DOM manipulation, I'd been using jQuery to create my elements, e.g.
var controlDiv = $('<div></div>');
var controlUI = $('<div class="alert alert-info"></div>');
controlDiv.append(controlUI);
var controlText = $('<div>Control text here</div>');
controlUI.append(controlText);
Doing this is fine, so long as you give the DOM node to the map (and not the jQuery element!) at the end, using controlUI[0] or controlUI.get(0), like this:
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(controlDiv[0]);
See also:
How to get the native DOM element from a jQuery object - jQuery FAQ
I followed the tutorial, which is very close to your code.
This line near the end needs to change
var churchControlDiv = new ChurchControl(churchControlDiv, map);
Replace churchControlDiv with churchControl or another name because churchControlDiv should not be overwritten.
See here http://jsfiddle.net/FTjnE/2/
I marked my changes with //CHANGED an alert for the click, and new map center
The general underlying cause of this issue seems to be the element or its properties being removed or otherwise made not present. Maps API is trying to find the zIndex in the style property.
I had this issue in a Vue app custom component that interacted with the Maps API controls. We resolved it by exercising more caution in the teardown of the component.
Basically you need to ensure that you don't add a null element to the control, and don't make the element or its properties null before removing it, by doing something funky with say, v-if.