I have a react-google-maps instance in my application which draws multiple location icons on google map. When there are more than one icons, sometimes, one icon, which have different location, gets stuck on another icon. The new icon is not clickable and gets removed automatically after giving a little nudge to the map (see image below)
IMO, the issue is with the google map's drawing multiple location icons.
Any idea, how can I fix this?
Here's how I am setting state when the component mounts
this.state = this.convertEntityStructureToComponentState(props.entities, props.hideInfoInitially, props.showDirections);
convertEntityStructureToComponentState() function looks like this:
convertEntityStructureToComponentState(entities, hideInfoInitially, showDirections=false) {
if (!entities || entities.length === 0) {
// send seattle longitude and latitude and set markers as null
const center = {
lat: 47.602743,
lng: -122.330626
};
return {
center: center,
markers: []
};
}
const markers = [];
let firstEntityReadings = null;
let destinationPosition = null;
let selectedEntityPosition = null;
let showPathLine = true;
for (let i = 0; i < entities.length; i++) {
if (entities[i].location) {
if (!firstEntityReadings) {
firstEntityReadings = {
lat: entities[i].location.lat,
lng: entities[i].location.lng
};
}
let showLocation = true;
if (entities[i].type === 'customer') {
destinationPosition = i;
} else {
if (this.isTimeInOnlineRange(entities[i].time)) {
selectedEntityPosition = i;
} else if (this.props.customerView) {
showLocation = false;
showPathLine = false;
}
}
if (showLocation) {
markers.push({
position: new google.maps.LatLng(entities[i].location.lat, entities[i].location.lng),
showInfo: false,
data: {
name: entities[i].name,
address: entities[i].address,
id: entities[i].id,
time: entities[i].time,
color: entities[i].type === 'customer' ? entities[i].color : '#ccc',
type: entities[i].type,
image_path: entities[i].image_path
}
});
}
}
}
//Optimize where we get previous location and don't call direction if it's the same
if (showDirections && destinationPosition != null && selectedEntityPosition != null && showPathLine) {
const DirectionsService = new google.maps.DirectionsService();
DirectionsService.route({
origin: new google.maps.LatLng(entities[selectedEntityPosition].location.lat, entities[selectedEntityPosition].location.lng),
destination: new google.maps.LatLng(entities[destinationPosition].location.lat, entities[destinationPosition].location.lng),
travelMode: google.maps.TravelMode.DRIVING,
}, (result, status) => {
if (status === google.maps.DirectionsStatus.OK) {
this.setState({
directions: result
});
}
});
} else {
this.setState({
directions: null
});
}
return {
center: firstEntityReadings,
markers: markers
};
}
Link to Github issue: https://github.com/tomchentw/react-google-maps/issues/805
Link to the gist of location map component: https://gist.github.com/arximughal/f91b7a922a4711e25ef82ed9ac6427b5
The issue was with the key and ref of the markers that I was drawing on the map. Generating a random ID for key fixed the issue properly.
Related
How to prevent the suggestedResult from collapsing after clicking result using SearchWidget?
CodePen, copied below
// An open data address search API for France
const url = "https://api-adresse.data.gouv.fr/";
const map = new Map({
basemap: "streets-vector"
});
const view = new MapView({
container: "viewDiv",
map: map,
center: [2.21, 46.22], // lon, lat
scale: 3000000
});
const customSearchSource = new SearchSource({
placeholder: "example: 8 Boulevard du Port",
// Provide a getSuggestions method
// to provide suggestions to the Search widget
getSuggestions: (params) => {
// You can request data from a
// third-party source to find some
// suggestions with provided suggestTerm
// the user types in the Search widget
return esriRequest(url + "search/", {
query: {
q: params.suggestTerm.replace(/ /g, "+"),
limit: 6,
lat: view.center.latitude,
lon: view.center.longitude
},
responseType: "json"
}).then((results) => {
// Return Suggestion results to display
// in the Search widget
return results.data.features.map((feature) => {
return {
key: "name",
text: feature.properties.label,
sourceIndex: params.sourceIndex
};
});
});
},
// Provide a getResults method to find
// results from the suggestions
getResults: (params) => {
// If the Search widget passes the current location,
// you can use this in your own custom source
const operation = params.location ? "reverse/" : "search/";
let query = {};
// You can perform a different query if a location
// is provided
if (params.location) {
query.lat = params.location.latitude;
query.lon = params.location.longitude;
} else {
query.q = params.suggestResult.text.replace(/ /g, "+");
query.limit = 6;
}
return esriRequest(url + operation, {
query: query,
responseType: "json"
}).then((results) => {
// Parse the results of your custom search
const searchResults = results.data.features.map((feature) => {
// Create a Graphic the Search widget can display
const graphic = new Graphic({
geometry: new Point({
x: feature.geometry.coordinates[0],
y: feature.geometry.coordinates[1]
}),
attributes: feature.properties
});
// Optionally, you can provide an extent for
// a point result, so the view can zoom to it
const buffer = geometryEngine.geodesicBuffer(
graphic.geometry,
100,
"meters"
);
// Return a Search Result
const searchResult = {
extent: buffer.extent,
feature: graphic,
name: feature.properties.label
};
return searchResult;
});
// Return an array of Search Results
return searchResults;
});
}
});
// Create Search widget using custom SearchSource
const searchWidget = new Search({
view: view,
sources: [customSearchSource],
includeDefaultSources: false
});
// Add the search widget to the top left corner of the view
view.ui.add(searchWidget, {
position: "top-right"
});
3d version of code sample above
There is no documented way to do this through the API, as far as I can tell. But by adding the esri-search--show-suggestions to the SearchWidget, the suggestions will reappear:
const searchWidget = new Search({
view: view,
sources: [customSearchSource],
includeDefaultSources: false,
//autoSelect: false,
goToOverride: function(view, { target, options }) {
view.goTo(target, options);
const widget = document.querySelector('.esri-search__container')
widget.className += ' esri-search--show-suggestions'
},
});
Working CodePen here
I have added markers to my videojs player. I want to jump to start time of every marker every time I click a particular button (say a next button). How should I do this. I know I have to change the currentTime but I am not getting how to solve the complete problem. I have start time of each marker. Any kind of input would be helpful.
Player.js
import assign from 'object-assign'
import cx from 'classnames'
import blacklist from 'blacklist'
import React from 'react'
module.exports = React.createClass({
displayName: 'VideoJS',
componentDidMount() {
var self = this;
var player = videojs(this.refs.video, this.props.options).ready(function() {
self.player = this;
self.player.on('play', self.handlePlay);
});
if(this.props.onPlayerInit) this.props.onPlayerInit(player);
player.markers({
markerStyle: {},
markers: [
{ startTime:10, endTime:15, time: 9.5, text: "compliance"},
{ startTime:20, endTime:25, time: 16, text: "compliance"},
{ startTime:30, endTime:38, time: 23.6,text: "compliance"},
{ startTime:51, endTime:55, time: 28, text: "compliance"}
]
});
},
handlePlay: function() {
if(this.props.onPlay) {
this.props.onPlay(this.player);
}
},
render() {
var props = blacklist(this.props, 'children', 'className', 'src', 'type', 'onPlay');
props.className = cx(this.props.className, 'videojs', 'video-js vjs-default-skin', 'vjs-big-play-centered');
assign(props, {
ref: 'video',
controls: true
});
return (
<div>
<video {... props}>
<source src={this.props.src} type={this.props.type}/>
</video>
</div>
)
}
});
Marker.js
(function($, video, undefined) {
//default setting
var defaultSetting = {
markerStyle: {
'border-radius': '0%',
},
markerTip: {
display: true,
text: function(marker) {
return "Break: "+ marker.text;
},
time: function(marker) {
return marker.time;
}
},
breakOverlay:{
display: false,
displayTime: 3,
text: function(marker) {
return "Break overlay: " + marker.overlayText;
},
style: {
'width':'100%',
'height': '20%',
'background-color': 'rgba(0,0,0,0.7)',
'color': 'white',
'font-size': '17px'
}
},
onMarkerClick: function(marker) {},
onMarkerReached: function(marker) {},
markers: []
};
// create a non-colliding random number
function generateUUID() {
var d = new Date().getTime();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d + Math.random()*16)%16 | 0;
d = Math.floor(d/16);
return (c=='x' ? r : (r&0x3|0x8)).toString(16);
});
return uuid;
};
function registerVideoJsMarkersPlugin(options) {
/**
* register the markers plugin (dependent on jquery)
*/
var setting = $.extend(true, {}, defaultSetting, options),
markersMap = {},
markersList = [], // list of markers sorted by time
videoWrapper = $(this.el()),
currentMarkerIndex = -1,
player = this,
markerTip = null,
breakOverlay = null,
overlayIndex = -1;
function sortMarkersList() {
// sort the list by time in asc order
markersList.sort(function(a, b){
return setting.markerTip.time(a) - setting.markerTip.time(b);
});
}
function addMarkers(newMarkers) {
// create the markers
$.each(newMarkers, function(index, marker) {
//console.log(index);
// console.log(marker);
marker.key = generateUUID();
//console.log(marker.key);
videoWrapper.find('.vjs-progress-control').append(
createMarkerDiv(marker));
// store marker in an internal hash map
markersMap[marker.key] = marker;
console.log(markersMap);
markersList.push(marker);
});
sortMarkersList();
}
function getPosition(marker){
return (setting.markerTip.time(marker) / player.duration()) * 100
}
function createMarkerDiv(marker, duration) {
var markerDiv = $("<div class='vjs-marker'></div>");
console.log(marker.length);
markerDiv.css(setting.markerStyle)
.css({"margin-left" : -parseFloat(markerDiv.css("width"))/2 + 'px',
"left" : getPosition(marker) + '%', })
.attr("data-marker-key", marker.key)
.attr("data-marker-time", setting.markerTip.time(marker));
console.log(setting.markerTip.time(marker));
// add user-defined class to marker
if (marker.class) {
markerDiv.addClass(marker.class);
}
// bind click event to seek to marker time
markerDiv.on('click', function(e) {
var preventDefault = false;
if (typeof setting.onMarkerClick === "function") {
// if return false, prevent default behavior
preventDefault = setting.onMarkerClick(marker) == false;
}
if (!preventDefault) {
var key = $(this).data('marker-key');
player.currentTime(setting.markerTip.time(markersMap[key]));
}
});
if (setting.markerTip.display) {
registerMarkerTipHandler(markerDiv);
}
return markerDiv;
}
function updateMarkers() {
// update UI for markers whose time changed
for (var i = 0; i< markersList.length; i++) {
var marker = markersList[i];
var markerDiv = videoWrapper.find(".vjs-marker[data-marker-key='" + marker.key +"']");
var markerTime = setting.markerTip.time(marker);
if (markerDiv.data('marker-time') != markerTime) {
markerDiv.css({"left": getPosition(marker) + '%'})
.attr("data-marker-time", markerTime);
}
}
sortMarkersList();
}
function removeMarkers(indexArray) {
// reset overlay
if (breakOverlay){
overlayIndex = -1;
breakOverlay.css("visibility", "hidden");
}
currentMarkerIndex = -1;
for (var i = 0; i < indexArray.length; i++) {
var index = indexArray[i];
var marker = markersList[index];
if (marker) {
// delete from memory
delete markersMap[marker.key];
markersList[index] = null;
// delete from dom
videoWrapper.find(".vjs-marker[data-marker-key='" + marker.key +"']").remove();
}
}
// clean up array
for (var i = markersList.length - 1; i >=0; i--) {
if (markersList[i] === null) {
markersList.splice(i, 1);
}
}
// sort again
sortMarkersList();
}
// attach hover event handler
function registerMarkerTipHandler(markerDiv) {
markerDiv.on('mouseover', function(){
var marker = markersMap[$(this).data('marker-key')];
markerTip.find('.vjs-tip-inner').text(setting.markerTip.text(marker));
// margin-left needs to minus the padding length to align correctly with the marker
markerTip.css({"left" : getPosition(marker) + '%',
"margin-left" : -parseFloat(markerTip.css("width"))/2 - 5 + 'px',
"visibility" : "visible"});
}).on('mouseout',function(){
markerTip.css("visibility", "hidden");
});
}
function initializeMarkerTip() {
markerTip = $("<div class='vjs-tip'><div class='vjs-tip-arrow'></div><div class='vjs-tip-inner'></div></div>");
videoWrapper.find('.vjs-progress-control').append(markerTip);
}
// show or hide break overlays
function updateBreakOverlay() {
if(!setting.breakOverlay.display || currentMarkerIndex < 0){
return;
}
var currentTime = player.currentTime();
var marker = markersList[currentMarkerIndex];
var markerTime = setting.markerTip.time(marker);
if (currentTime >= markerTime &&
currentTime <= (markerTime + setting.breakOverlay.displayTime)) {
if (overlayIndex != currentMarkerIndex){
overlayIndex = currentMarkerIndex;
breakOverlay.find('.vjs-break-overlay-text').html(setting.breakOverlay.text(marker));
}
breakOverlay.css('visibility', "visible");
} else {
overlayIndex = -1;
breakOverlay.css("visibility", "hidden");
}
}
// problem when the next marker is within the overlay display time from the previous marker
function initializeOverlay() {
breakOverlay = $("<div class='vjs-break-overlay'><div class='vjs-break-overlay-text'></div></div>")
.css(setting.breakOverlay.style);
videoWrapper.append(breakOverlay);
overlayIndex = -1;
}
function onTimeUpdate() {
onUpdateMarker();
updateBreakOverlay();
}
function onUpdateMarker() {
/*
check marker reached in between markers
the logic here is that it triggers a new marker reached event only if the player
enters a new marker range (e.g. from marker 1 to marker 2). Thus, if player is on marker 1 and user clicked on marker 1 again, no new reached event is triggered)
*/
var getNextMarkerTime = function(index) {
if (index < markersList.length - 1) {
return setting.markerTip.time(markersList[index + 1]);
}
// next marker time of last marker would be end of video time
return player.duration();
}
var currentTime = player.currentTime();
var newMarkerIndex;
if (currentMarkerIndex != -1) {
// check if staying at same marker
var nextMarkerTime = getNextMarkerTime(currentMarkerIndex);
if(currentTime >= setting.markerTip.time(markersList[currentMarkerIndex]) &&
currentTime < nextMarkerTime) {
return;
}
// check for ending (at the end current time equals player duration)
if (currentMarkerIndex === markersList.length -1 &&
currentTime === player.duration()) {
return;
}
}
// check first marker, no marker is selected
if (markersList.length > 0 &&
currentTime < setting.markerTip.time(markersList[0])) {
newMarkerIndex = -1;
} else {
// look for new index
for (var i = 0; i < markersList.length; i++) {
nextMarkerTime = getNextMarkerTime(i);
if(currentTime >= setting.markerTip.time(markersList[i]) &&
currentTime < nextMarkerTime) {
newMarkerIndex = i;
break;
}
}
}
// set new marker index
if (newMarkerIndex != currentMarkerIndex) {
// trigger event
if (newMarkerIndex != -1 && options.onMarkerReached) {
options.onMarkerReached(markersList[newMarkerIndex]);
}
currentMarkerIndex = newMarkerIndex;
}
}
// setup the whole thing
function initialize() {
if (setting.markerTip.display) {
initializeMarkerTip();
}
// remove existing markers if already initialized
player.markers.removeAll();
addMarkers(options.markers);
if (setting.breakOverlay.display) {
initializeOverlay();
}
onTimeUpdate();
player.on("timeupdate", onTimeUpdate);
}
// setup the plugin after we loaded video's meta data
player.on("loadedmetadata", function() {
initialize();
});
// exposed plugin API
player.markers = {
getMarkers: function() {
return markersList;
},
next : function() {
// go to the next marker from current timestamp
var currentTime = player.currentTime();
for (var i = 0; i < markersList.length; i++) {
var markerTime = setting.markerTip.time(markersList[i]);
if (markerTime > currentTime) {
player.currentTime(markerTime);
break;
}
}
},
prev : function() {
// go to previous marker
var currentTime = player.currentTime();
for (var i = markersList.length - 1; i >=0 ; i--) {
var markerTime = setting.markerTip.time(markersList[i]);
// add a threshold
if (markerTime + 0.5 < currentTime) {
player.currentTime(markerTime);
break;
}
}
},
add : function(newMarkers) {
// add new markers given an array of index
addMarkers(newMarkers);
},
remove: function(indexArray) {
// remove markers given an array of index
removeMarkers(indexArray);
},
removeAll: function(){
var indexArray = [];
for (var i = 0; i < markersList.length; i++) {
indexArray.push(i);
}
removeMarkers(indexArray);
},
updateTime: function(){
// notify the plugin to update the UI for changes in marker times
updateMarkers();
},
reset: function(newMarkers){
// remove all the existing markers and add new ones
player.markers.removeAll();
addMarkers(newMarkers);
},
destroy: function(){
// unregister the plugins and clean up even handlers
player.markers.removeAll();
breakOverlay.remove();
markerTip.remove();
player.off("timeupdate", updateBreakOverlay);
delete player.markers;
},
};
}
videojs.plugin('markers', registerVideoJsMarkersPlugin);
})(jQuery, window.videojs);
I've successfully implemented angular-google-maps on a local project of mine with list of markers that's being put on my map.
But how can I get the "Visible Markers In Bounds"? (the angular-way)
jQuery example: http://jsfiddle.net/glafarge/mbuLw/
At the moment I've attached an event inside my controller on "idle" which gets triggered whenever map changes (zoom et). The foreach loops through my markers, but I'm just not sure how to perform the "contains(marker.getPosition())" because that's not a function in the angular version of google maps.
events: {
idle: function () {
console.log("change triggered");
angular.forEach($scope.markers, function(marker, key) {
console.log("set visible markers here");
});
}
}
Bounds are accessible in $scope.map.bounds and looks like this:
$scope.map.bounds = {
northeast: {
latitude: 51.219053,
longitude: 4.404418
},
southwest: {
latitude: -51.219053,
longitude: -4.404418
}
}
The map object is accessible in $scope.mapRef:
uiGmapIsReady.promise().then(function (map_instances) {
$scope.mapRef = $scope.map.control.getGMap();
});
Inspired by MayK's answer I solved it with the following:
idle: function (map) {
$timeout(function() {
var visibleMarkers = [];
angular.forEach($scope.markers, function(marker, key) {
if ($scope.map.bounds.southwest.latitude < marker.coords.latitude
&& marker.coords.latitude < $scope.map.bounds.northeast.latitude
&& $scope.map.bounds.southwest.longitude < marker.coords.longitude
&& marker.coords.longitude < $scope.map.bounds.northeast.longitude) {
visibleMarkers.push(marker);
}
});
$scope.visibleMarkers = visibleMarkers;
}, 0);
}
add an events for bounds_changed whenever there is a change, iterate over your markers: for each marker if this condition (bounds.sw.lat< marker.lat < bounds.ne.lat and bounds.sw.lon < marker.lon < bounds.ne.lon ) is true, then your marker is visible, so you can append it to a list of visible markers.
vm.map.events = {
bounds_changed: function(map) {
vm.visibleMarkers=[];
for (var i = 0; i < vm.beaconMarkers.length; i++) {
if (vm.map.bounds.southwest.latitude < vm.beaconMarkers[i].latitude && vm.beaconMarkers[i].latitude < vm.map.bounds.northeast.latitude && vm.map.bounds.southwest.longitude < vm.beaconMarkers[i].longitude && vm.beaconMarkers[i].longitude< vm.map.bounds.northeast.longitude) {
vm.visibleMarkers.push(
vm.beaconMarkers[i]
);
}
}
}
};
Sadly I just discovered a bug in the "accepted solution". If you for example drag your map northeast of Asia it will return a negative longitude which makes the condition return false.
For now I resolved it by using google maps build-in functionality like beneath. However though, I don't think this is the best approach so I'm still looking for a better approach:
var visibleMarkers = [];
var bounds = $scope.mapRef.getBounds();
angular.forEach($scope.markers, function(marker, key) {
var gMarker = new google.maps.Marker({
position: {
lat: marker.coords.latitude,
lng: marker.coords.longitude
}
});
if(bounds.contains(gMarker.getPosition())===true) {
visibleMarkers.push(marker);
}
});
$scope.visibleMarkers = visibleMarkers;
Used Map Object's getBounds() method then use contains() method to detect whether the lat lng is within bound
I don't know why the marker and zoom is not working and here is the code:
function initialize() {
if (document.getElementById("themap") != null) {
$.post("http://localhost/projects/php/invoice/getmapinfo.php", {
postalcode: $("#postalcode").html()
},
function(data, status) {
if (data == "-1" || data == "-2" || data == "-3") {
} else {
var apos = data.split(',');
var myCenter = new google.maps.LatLng(parseFloat(apos[0]), parseFloat(apos[1]));
var mapProp = {
center:myCenter,
zoom:10,
mapTypeId:google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("themap"),mapProp);
var marker = new google.maps.Marker({
position:myCenter,
});
marker.setMap(map);
}
}
);
}
}
$(document).ready(
function() {
$(document).ajaxStop(
function(){
initialize();
}
);
The map will show up but its is static. Do I need to get a key or not because it is run on my internal web server for me only or am I missing a step.
Thanks if you can help.
I am adding 'place_changed' listener it's not working first time when I search the address while it's working perfectly afterwards. Can anyone tell me what should be done. here is my code. Thanks in advance for your support
var events = {
places_changed: function (searchBox) {
var gPlace = new google.maps.places.Autocomplete(document.getElementById('map-search-box'));
google.maps.event.addListener(gPlace, 'place_changed', function() {
var place = gPlace.getPlace();
var a =place.geometry.location;
$scope.myLocation = {
lng : place.geometry.location.D,
lat: place.geometry.location.k
}
abc = {
coords: {
latitude: place.geometry.location.k,
longitude:place.geometry.location.D
}
}
$scope.drawMap(abc);
});
}
}
Thanks KayAnn I have solved the issue. Actually I never checked in the function (searchBox) {} searchBox it's object which were proving all the data I required so here is updated code that is working perfectly
var events = {
places_changed: function (searchBox) {
var lat = searchBox.getPlaces()[0].geometry.location.k;
var lgn = searchBox.getPlaces()[0].geometry.location.D;
$scope.myLocation = {
lng : searchBox.getPlaces()[0].geometry.location.D,
lat: searchBox.getPlaces()[0].geometry.location.k
}
abc = {
coords: {
latitude: searchBox.getPlaces()[0].geometry.location.k,
longitude:searchBox.getPlaces()[0].geometry.location.D
}
}
$scope.drawMap(abc); } }