Infowindow on Marker Cluster - #react-google-maps - google-maps

Is there any way to show an Info Window on a marker cluster in #react-google-maps, we have displayed an info window on a single marker which works absolutely fine but didn't find a way to display the same for a marker cluster.
What have I already tried?
I have tried to add an info window like an inner component inside the marker cluster
<GoogleMap setMap={setMap}>
<MarkerClustererF onMouseOver={(ele)=>{setMarkerClusterTitle(ele);}}>
{clusterer =>
locs.map((loc: any) => (
<>
<CustomMarker
position={loc}
clusterer={clusterer}
nodeData={loc.nodeData}
primaryKeyColumnName={props.selectedTableInfo.pkColumnName}
setNodeData={setCurrentMapNodeData}
handleMapInfoWindowOpenClose={handleMapInfoWindowOpenClose}
/>
<InfoWindow position={loc}>
<div><strong>API : </strong>Working</div>
</InfoWindow>
</>
))
}
</MarkerClustererF>
</GoogleMap>
But this doesn't work for clusters this will just add an info window for all the markers, what we need is an info window on clusters that pops up on hover.

First, you need to create infoWindow instance in your Component, and Open and Close in onMouseOver, onMouseOut event handler.
const GoogleMapContainer = () => {
const infoWindow = new google.maps.InfoWindow();
.....
const handleOpenInfoWindow = useCallback((cluster) => {
infoWindow.setContent(`Current Cluster Length: ${cluster.markers.length}`);
infoWindow.setPosition(cluster.getCenter());
infoWindow.open(cluster.map);
const handleCloseInfoWindow = useCallback((cluster) => {
infoWindow.close(cluster.map);
}, []);
return (
<GoogleMap setMap={setMap}>
<MarkerCluster onMouseOver={(cluster) => handleOpenInfoWindow(cluster)} onMouseOut={(cluster) => handleCloseInfoWindow(cluster)}
// Your markers array map here to show markers
</MarkerCluster>
</GoogleMap>
);
}
export default GoogleMapContainer

Related

Vuejs + Google Maps > document.getElementById and this.refs returning null for a div

I am trying to add google maps to my vuejs application following this tutorial here: https://markus.oberlehner.net/blog/using-the-google-maps-api-with-vue/
I have a div with id="maps" as well as ref="mapsection". I tried binding maps instance with the div with both document.getElementById as well as this.ref but I get the null/undefined error. Can someone please advise what I am doing wrong? I see the div created when I go I to inspect mode.
I have tried both of the following where "mapsection" is the ref and "map" is the id for the div.
const map = new google.maps.Map(this.refs.mapsection);
const map = new google.maps.Map(document.getElementById('map'));
View code:
<el-row>
<div ref="mapsection" id="map" style="width:100%;height:400px">
</div>
</el-row>
Script code:
async mounted() {
try {
console.log(document.getElementById('map')); //returns null
const google = await gmapsInit();
const geocoder = new google.maps.Geocoder();
const map = new google.maps.Map(document.getElementById('map')); //tried this.refs.mapsection as well.
const locations = [{
position: {
lat: 48.160910,
lng: 16.383330
}
}]
geocoder.geocode({ address: 'Austria' }, (results, status) => {
if (status !== 'OK' || !results[0]) {
throw new Error(status);
}
map.setCenter(results[0].geometry.location);
map.fitBounds(results[0].geometry.viewport);
const markers = locations.map(x => new google.maps.Marker({ ...x, map }));
});
} catch (error) {
console.error(error);
}
}
Errors I am getting:
with this.refs.mapsection > TypeError: Cannot read property 'mapsection' of undefined
with document.getElementById('maps') > TypeError: Cannot read property 'firstChild' of null
The referenced tutorial does not use el-row. Your problem has nothing to do with google maps.
To debug try one of these approaches.
put all of mounted in an async $nextTick to ensure render.
move your code out of el-row, el-table, etc, and into a div to isolate the issue.
refs in loops is often an array, so when you finally get this working, it is something to consider.

Carto-VL animation controls not showing up on map

The sample Carto-VL animation control code doesn't appear on my animated map.
I'm creating an animation of tree population change since the last ice age in Carto VL, and I want the user to be able to change the duration and play/pause the animation. I've pasted the code from the example pages into my map.
const $progressRange = document.getElementById('js-progress-range');
const $playButton = document.getElementById('js-play-button');
const $pauseButton = document.getElementById('js-pause-button');
const $durationRange = document.getElementById('js-duration-range');
const $currentTime = document.getElementById('js-current-time');
// Listen to interaction events with the UI
$playButton.addEventListener('click', () => {
viz.variables.animation.play();
});
$pauseButton.addEventListener('click', () => {
viz.variables.animation.pause();
});
$durationRange.addEventListener('change', () => {
viz.variables.duration = parseInt($durationRange.value, 10);
});
// Update progress bar each 100 milliseconds
function updateProgress () {
$progressRange.value = viz.variables.animation.getProgressPct();
$currentTime.innerText = viz.variables.animation.getProgressValue();
}
setInterval(updateProgress, 100);
I expect the animation control box to show up on the map, but it only appears if there's an error in the code causing the map not to appear. Even then, I cannot interact with it.

react-google-maps and google event listeners - how to catch events?

In my app I'm using react-google-maps (v.6.x.x) to handle api for Google Maps. I'm using markers and infobox to show proper information. Infobox with enableEventPropagation set to true, propagates events to map layer through the infobox - what does that mean? When I have infobox - aka infowindow whe I click on it, and underneath is placed marker, this marker is 'clicked' in first place, and than any html element in infobox. If enableEventPropagation is false - nothing is propagated. So my question is - is there any possibility to add google event listener for react component, for example code:
let infobox = (<InfoBox
onDomReady={() => props.infoBoxReady(infobox)}
defaultPosition={new google.maps.LatLng(props.activeMarker.location[1], props.activeMarker.location[0])}
options={{
closeBoxURL: ``, enableEventPropagation: true, pane: 'mapPane',
position: new google.maps.LatLng(props.activeMarker.location[1], props.activeMarker.location[0]),
alignBottom: true,
pixelOffset: new google.maps.Size(-120, 0)
}}>
How I can use this code to use Google Event Listener
google.maps.event.addListener(infobox, 'domready', function ()
Im getting this kind error
Any clue, how can I set listener for it, or maybe there are other options to set listeners for google map - unfortunately I've to use this library and handle clicks on infobox
The only way I was able to prevent clicks through to the map (stop propagation) and still be able to click items in the in InfoBox is by setting "enableEventPropagation: false" and then adding listeners for the items in the InfoBox on the "onDomReady" prop. This makes sure that the listeners are attached after the InfoBox is rendered. Not sure if thats the best solution, but it did work. Hope that helps someone.
<InfoBox
defaultPosition={new LatLng(marker.position.lat, marker.position.lng)}
onDomReady={
() => {
let closeButton = $('a.close');
let someLink = $('a.info-box-profile');
closeButton.on('click', () => {
// do something with the click
});
someLink.on('click', () => {
// do something with the click
});
}
}
options={{
pixelOffset: new Size(-145, 30),
closeBoxURL: '',
enableEventPropagation: false,
boxStyle: {
overflow: "hidden",
width: "320px",
}
}}>
I wrote this wrapper component that places a <Rectangle/> over the entire map to stop clicks being passed through to the map below. Whilst still allowing you to click things inside the <infoBox/>.
This allows enableEventPropagation to be set to true without creating problems. I then use mouseOver and mouseOut to control how the rectangle works. In my case I use clicking on the rectangle to close my <InfoBox/>. You could just as easily hide and show it.
/* global google */
/* eslint-disable jsx-a11y/no-static-element-interactions */
import React from 'react';
import PropTypes from 'prop-types';
import { Rectangle } from 'react-google-maps';
import GoogleInfoBox from 'react-google-maps/lib/components/addons/InfoBox';
const cardWidth = 235;
const boxShadow = 25; // space for outer shadow
const iconHeight = 2; // the actual height is 48px but we use 6px instead to hide the icon but keep it's shadow
class InfoBox extends React.Component {
constructor(props) {
super(props);
this.closable = true;
}
onClose = () => {
if (this.closable) this.props.onClose();
}
onHover = () => {
this.closable = false;
}
onMouseOut = () => {
this.closable = true;
}
render() {
const { children, position, onClose, type } = this.props;
return (
<div className="info-box">
<Rectangle
options={{ fillColor: '#ffffff', fillOpacity: 0.7, zIndex: 100 }}
bounds={{ north: 90, south: -90, east: 180, west: -180 }}
onClick={this.onClose}
/>
<GoogleInfoBox
position={new google.maps.LatLng(position.lat, position.lng)}
onCloseClick={onClose}
options={{
alignBottom: true,
disableAutoPan: false,
pixelOffset: new google.maps.Size(-(cardWidth / 2) - boxShadow, -iconHeight),
maxWidth: width,
isHidden: false,
visible: true,
pane: 'floatPane',
enableEventPropagation: true,
}}
>
<div
onMouseOver={this.onHover}
onFocus={this.onHover}
onMouseOut={this.onMouseOut}
onBlur={this.onMouseOut}
>
{ children }
</div>
</GoogleInfoBox>
</div>
);
}
}
InfoBox.propTypes = {
children: PropTypes.element.isRequired,
position: PropTypes.shape({
lat: PropTypes.number.isRequired,
lng: PropTypes.number.isRequired,
}).isRequired,
onClose: PropTypes.func,
type: PropTypes.string,
};
export default InfoBox;
You can access the listeners via props on the InfoBox component.
Check them out here: Github docs
You're already using one - onDomReady - there's also:
onCloseClick: `closeclick`,
onContentChanged: `content_changed`,
onPositionChanged: `position_changed`,
onZIndexChanged: `zindex_changed`,

The simpliest way to subscribe for an observableArray in knockoutjs

I have an observableArray with some markers, for example:
var markers = ko.observableArray([
{
id: 0,
title: ko.observable("Marker 0"),
lat: ko.observable(55.31),
lng: ko.observable(11)
},
{
id: 1,
title: ko.observable("Marker 1"),
lat: ko.observable(57.20),
lng: ko.observable(15.5)
}
]);
This array is sent to some MapWidget object, that has to create google map markers for each element. It has to move markers in case lat,lng observables change, change marker's title in case title observable changes and so on.
That means in MapWidget there's some array of googlemap markers, and it should be connected with the given observableArray. What is the best and the simpliest way to connect them?
Upd. More details about MapWidget.
MapWidget is some object that has an access to some google maps map object, and it receives as an argument an observableArray with markers like that one above.
var MapWidget = function(markers) {
var div = $("#map").get(0);
this.map = new gmaps.Map(div);
/*
The magic goes here:
markers is an observableArray, we subscribe for it's changes,
create gmaps.marker for each new element,
destroy in case of destroying them from array,
move and rename each marker in case of corresponding changes
*/
}
You could subscribe to your array like this:
ar.subscribe(function() {
// clean and redraw all markers
});
If you do that, you will receive a notification when an item is added/removed to/from the array. BUT not when an property of an item is modified.
If you want to update your google maps markers based on individual property changes in items, you could implement a simple dirty flag mechanism and then subscribe individually to each item. Since each item has an id, which I presume is unique, you could create a map with key/value pairs being the id and the map widget.
So, given each individual item:
var item = function(data) {
var self = this;
self.isChanged = ko.observable(self);
self.id = data.id;
self.title = ko.observable(data.title);
self.title.subscribe(function() { self.isChanged(self); self.isChanged.valueHasMutated(); });
self.lat = ko.observable(data.lat);
self.lat.subscribe(function() { self.isChanged(self); self.isChanged.valueHasMutated(); });
self.lng = ko.observable(data.lng);
self.lng.subscribe(function() { self.isChanged(self); self.isChanged.valueHasMutated(); });
}
And given a hypothetic map where you keep a link between your markers and the items:
var markersMap = [];
markersMap[0] = yourGoogleMapWidget;
You can then subscribe to track changes on items like this:
ar[0].isChanged.subscribe(function(item) {
var myGMapMarker = markersMap[item.id()];
// update your marker, or destroy and recreate it...
});

egmap Google Maps Yii not saving data to database

I am using egmaps extensions with Yii application it is a very brilliant application. how ever I am having trouble populating database. my database table hotel has two attributes for long and lat. I am not expert in AJAX but i think that ajax is calling controler method. At present I donot have anything in controler method because I donot know what data will come and how?
my code so far is BUT I THINK THIS AJAX IS NOT CALLING THE CONTROLLER SaveCoordinates action
$gMap->zoom = 6;
$mapTypeControlOptions = array(
'position' => EGMapControlPosition::RIGHT_TOP,
'style' => EGMap::MAPTYPECONTROL_STYLE_HORIZONTAL_BAR
);
$gMap->mapTypeId = EGMap::TYPE_ROADMAP;
$gMap->mapTypeControlOptions = $mapTypeControlOptions;
// Preparing InfoWindow with information about our marker.
$info_window_a = new EGMapInfoWindow("<div class='gmaps-label' style='color: #000;'>Hi! I'm your marker!</div>");
// Setting up an icon for marker.
$icon = new EGMapMarkerImage("http://google-maps-icons.googlecode.com/files/car.png");
$icon->setSize(32, 37);
$icon->setAnchor(16, 16.5);
$icon->setOrigin(0, 0);
// Saving coordinates after user dragged our marker.
$dragevent = new EGMapEvent('dragend', 'function (event) { $.ajax({
type:"POST",
url:"'.$this->createUrl('hotel/savecoords').'/'.$model->id.'",
data:({lat: event.latLng.lat(), lng: event.latLng.lng()}),
cache:false,
});}', false, EGMapEvent::TYPE_EVENT_DEFAULT);
if($model->long)
{
// If we have already created marker - show it
$marker = new EGMapMarker($model->lat, $model->long, array('title' => Yii::t('catalog', $model->name),
'icon'=>$icon, 'draggable'=>true), 'marker', array('dragevent'=>$dragevent));
$marker->addHtmlInfoWindow($info_window_a);
$gMap->addMarker($marker);
$gMap->setCenter($model->lat, $model->long);
$gMap->zoom = 16;
}
else
{
// Setting up new event for user click on map, so marker will be created on place and respectful event added.
$gMap->addEvent(new EGMapEvent('click',
'function (event) {var marker = new google.maps.Marker({position: event.latLng, map: '.$gMap->getJsName().
', draggable: true, icon: '.$icon->toJs().'}); '.$gMap->getJsName().
'.setCenter(event.latLng); var dragevent = '.$dragevent->toJs('marker').
'; $.ajax({'.
'"type":"POST",'.
'"url":"'.$this->createUrl('hotel/savecoords')."/".$model->id.'",'.
'"data":({"lat": event.latLng.lat(), "lng": event.latLng.lng()}),'.
'"cache":false,'.
'}); }', false, EGMapEvent::TYPE_EVENT_DEFAULT_ONCE));
}
$gMap->renderMap(array(), Yii::app()->language);
I used POST, and it works. Maybe in your Model's rules params are not specified?
Solved in case other may get stuck I solved it by changing POST to GET and URL Line
url:"'.$this->createUrl('hotel/savecoords').'/'.$model->id.'", '
to
url':'".$this->createUrl('hotel/savecoords', array('id'=>$model->id))."',`
and controller code is
public function actionSaveCoords($id)
{
$model=$this->loadModel($id);
// Uncomment the following line if AJAX validation is needed
//
// $this->performAjaxValidation($model);
if(isset ($_GET['lat']))
$model->lat = $_GET['lat'];
if(isset ($_GET['lat']))
$model->long = $_GET['lng'];
if($model->save())
{
echo 'Thank you for registring your place with '.Yii::app()->name;
}
$this->render('view',array(
'model'=>$model,
));
}