What I have :
As per Google suggestion we have used MVC object and the events DistanceWidget & RadiusWidget to display the radius while re-sizing the circle which is working fine for existing circle (circle drawn by DistanceWidget).
Fiddle Demo
What I need :
I need to show the radius of the circle while drawing a new circle. The event DistanceWidget & RadiusWidget are used only for existing circle (circle drawn by DistanceWidget) not for new circle (user drawn circle by using DrawingManager tool).
Is it possible to show DistanceWidget for creating new circle?
Fiddle
a nice challenge indeed. As #DaveAlperovich has commented, you can't use the DrawingManager to retrieve this piece of information; While drawing, you don't have any access to the circle; You have to wait for the DrawingManager to trigger the circlecomplete event to get a reference to this circle.
Nevertheless, if you can't have a real manager, just fake it.
See the snippet and the description right below.
var FakeDrawer = function(controlDiv, map) {
var self = this;
/* Initialization, some styling ... */
self._map = map;
self.initControls(controlDiv);
/* Setup the click event listener: drawingmode for the circle control */
google.maps.event.addDomListener(self._controls.circle, 'click', function() {
/* Ensure consistency */
self.reset();
/* Bind the drawing mode */
self._map.setOptions({
draggableCursor: "crosshair"
});
self._drawListener = self._map.addListener('mousedown', self.drawingMode(self));
});
/* Just reset things for the stop controls */
google.maps.event.addDomListener(self._controls.stop, 'click', function() {
self.reset();
});
};
FakeDrawer.prototype.drawingMode = function(self) {
return function(center) {
/* Let's freeze the map during drawing */
self._map.setOptions({
draggable: false
});
/* Create a new circle which will be manually scaled */
var circle = new google.maps.Circle({
fillColor: '#000',
fillOpacity: 0.3,
strokeWeight: 2,
clickable: false,
editable: false,
map: self._map,
radius: 1,
center: center.latLng,
zIndex: 1
});
/* Update the radius on each mouse move */
var onMouseMove = self._map.addListener('mousemove', function(border) {
var radius = 1000 * self.distanceBetweenPoints(center.latLng, border.latLng);
circle.setRadius(radius);
/* Here is the feature, know the radius while drawing */
google.maps.event.trigger(self, 'drawing_radius_changed', circle);
});
/* The user has finished its drawing */
google.maps.event.addListenerOnce(self._map, 'mouseup', function() {
/* Remove all listeners as they are no more required */
google.maps.event.removeListener(onMouseMove);
circle.setEditable(true);
/* Restore some options to keep a consistent behavior */
self.reset();
/* Notify listener with the final circle */
google.maps.event.trigger(self, 'circlecomplete', circle);
});
};
};
FakeDrawer.prototype.reset = function() {
var self = this;
self._map.setOptions({
draggableCursor: "",
draggable: "true"
});
/* Remove any applied listener */
if (self._drawListener) {
google.maps.event.removeListener(self._drawListener);
}
};
/* Create views and associated css */
FakeDrawer.prototype.initControls = function(controlDiv) {
var self = this;
function createControlUI(title, image) {
var controlUI = document.createElement('div');
controlUI.style.backgroundColor = '#fff';
controlUI.style.border = '1px solid rgba(0, 0, 0, .15)';
controlUI.style.boxShadow = '1 4px -1px rgba(0, 0, 0, .3)';
controlUI.style.marginTop = '10px';
controlUI.style.textAlign = 'center';
controlUI.style.width = '25px';
controlUI.style.height = '25px';
controlUI.style.display = 'inline-block';
controlUI.title = title;
if (image == "circle") {
controlUI.style.borderLeft = "none";
}
var controlImgWrapper = document.createElement('div');
controlImgWrapper.style.width = '16px';
controlImgWrapper.style.height = '16px';
controlImgWrapper.style.overflow = 'hidden';
controlImgWrapper.style.display = 'inline-block';
controlImgWrapper.style.marginTop = '4px';
controlUI.appendChild(controlImgWrapper);
var imageOffset = {
"circle": 0,
"openhand": -9 * 16
}[image];
var controlImg = document.createElement('img');
controlImg.src = 'https://maps.gstatic.com/mapfiles/drawing.png';
controlImg.style.marginTop = imageOffset + "px";
controlImgWrapper.appendChild(controlImg);
var focusBackground = function() {
controlUI.style.backgroundColor = '#eee';
};
var unfocusBackground = function() {
controlUI.style.backgroundColor = "#fff";
};
controlImg.addEventListener('mouseenter', focusBackground);
controlImg.addEventListener('mouseout', unfocusBackground);
controlUI.addEventListener('mouseenter', focusBackground);
controlUI.addEventListener('mouseout', unfocusBackground);
return controlUI;
}
self._controls = {
circle: createControlUI("Draw a circle", "circle"),
stop: createControlUI("Stop drawing", "openhand"),
};
controlDiv.appendChild(self._controls.stop);
controlDiv.appendChild(self._controls.circle);
};
FakeDrawer.prototype.distanceBetweenPoints = function(p1, p2) {
if (!p1 || !p2) {
return 0;
}
var R = 6371;
var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = R * c;
return d;
};
function InitializeMap() {
var latlng = new google.maps.LatLng(29.760193, -95.36939);
var myOptions = {
zoom: 12,
center: latlng,
zoomControl: true,
mapTypeId: google.maps.MapTypeId.ROADMAP,
disableDefaultUI: true
};
var map = new google.maps.Map(document.getElementById("map"), myOptions);
/* Add a custom control */
var fakeDrawerDiv = document.createElement('div');
var fakeDrawer = new FakeDrawer(fakeDrawerDiv, map);
fakeDrawerDiv.index = 1;
map.controls[google.maps.ControlPosition.TOP_CENTER].push(fakeDrawerDiv);
var updateInfo = function(circle) {
document.getElementById("info").innerHTML = "Radius: " + circle.getRadius();
};
google.maps.event.addListener(fakeDrawer, 'drawing_radius_changed', updateInfo);
google.maps.event.addListener(fakeDrawer, 'circlecomplete', function(circle) {
google.maps.event.addListener(circle, 'radius_changed', function() {
updateInfo(circle);
});
});
}
google.maps.event.addDomListener(window, 'load', InitializeMap);
html,
body {
height: 100%;
margin: 0px;
padding: 0px
}
#map {
height: 80%;
width: 100%;
}
<script src="https://maps.googleapis.com/maps/api/js?v=3&libraries=drawing&ext=.js"></script>
<div id="map"></div>
<div id="info"></div>
Step 1: Create a custom control
Somewhere in the file or as an external library:
var FakeDrawer = function (controlDiv, map) {
var self = this;
/* Initialization, some styling ... */
self._map = map;
self.initControls(controlDiv);
};
FakeDrawer.prototype.initControls(controlDiv) {
var self = this;
function createControlUI (title, image) {
var controlUI = document.createElement('div');
/* ... See the snippet for details .. just some styling */
return controlUI;
}
self._controls = {
circle: createControlUI("Draw a circle", "circle"),
stop: createControlUI("Stop drawing", "openhand"),
};
controlDiv.appendChild(self._controls.stop);
controlDiv.appendChild(self._controls.circle);
};
Step 2: Add some sugars
This are functions that we may use; Highly inspired from your JsFiddle :)
A reset method to recover a consistent state when needed:
FakeDrawer.prototype.reset = function () {
var self = this;
self._map.setOptions({
draggableCursor: "",
draggable: "true"
});
/* Remove any applied listener */
if (self._drawListener) { google.maps.event.removeListener(self._drawListener) ; }
};
And, a distance computer:
FakeDrawer.prototype.distanceBetweenPoints = function (p1, p2) {
if (!p1 || !p2) {
return 0;
}
var R = 6371;
var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = R * c;
return d;
};
Step 3: Create your own drawing mode
Now that we have some controls, we have to define their behavior. The stop control is straightforward; Let's have a look to the circle control.
FakeDrawer.prototype.drawingMode = function (self) {
return function (center) {
/* Let's freeze the map during drawing */
self._map.setOptions({draggable: false});
/* Create a new circle which will be manually scaled */
var circle = new google.maps.Circle({
fillColor: '#000',
fillOpacity: 0.3,
strokeWeight: 2,
clickable: false,
editable: false,
map: self._map,
radius: 1,
center: center.latLng,
zIndex: 1
});
/* Update the radius on each mouse move */
var onMouseMove = self._map.addListener('mousemove', function (border) {
var radius = 1000 * self.distanceBetweenPoints(center.latLng, border.latLng);
circle.setRadius(radius);
/* Here is the feature, know the radius while drawing */
google.maps.event.trigger(self, 'drawing_radius_changed', circle);
});
/* The user has finished its drawing */
google.maps.event.addListenerOnce(self._map, 'mouseup', function () {
/* Remove all listeners as they are no more required */
google.maps.event.removeListener(onMouseMove);
circle.setEditable(true);
/* Restore some options to keep a consistent behavior */
self.reset();
/* Notify listener with the final circle */
google.maps.event.trigger(self, 'circlecomplete', circle);
});
};
};
Step 4: Bind controls
Now that everything is okay, let's add some listeners to the initial version of the constructor so that each control has a corresponding action when clicked.
var FakeDrawer = function (controlDiv, map) {
var self = this;
/* Initialization, some styling ... */
self._map = map;
self.initControls(controlDiv);
/* Setup the click event listeners: drawingmode */
google.maps.event.addDomListener(self._controls.circle, 'click', function() {
/* Ensure consistency */
self.reset();
/* Only drawingmode */
self._map.setOptions({draggableCursor: "crosshair"});
self._drawListener = self._map.addListener('mousedown', self.drawingMode(self));
});
google.maps.event.addDomListener(self._controls.stop, 'click', function () {
self.reset();
});
};
Step 5: Use it!
Assuming that your map has been initialized correctly.
Inside your map init function:
var fakeDrawerDiv = document.createElement('div');
var fakeDrawer = new FakeDrawer(fakeDrawerDiv, map);
fakeDrawerDiv.index = 1;
map.controls[google.maps.ControlPosition.TOP_CENTER].push(fakeDrawerDiv);
var updateInfo = function (circle) {
document.getElementById("info").innerHTML = "Radius: " + circle.getRadius();
};
google.maps.event.addListener(fakeDrawer, 'drawing_radius_changed', updateInfo);
google.maps.event.addListener(fakeDrawer, 'circlecomplete', function (circle) {
google.maps.event.addListener(circle, 'radius_changed', function () {
updateInfo(circle);
});
});
Enjoy, hope it will help.
Related
I'm currently working on a javascript animation that transitions details on an event action - however, I am having an issue removing a certain rogue div.
I have my init function as follows:
var demo = (function(window, undefined) {
function init() {
_mapPolygons(pattern);
_bindCards();
$(document.getElementsByClassName("card__overlay")).addClass(CLASSES.overlayHidden);
};
return {
init: init
};
})(window);
window.onload = demo.init;
The full snippet is here:
'use strict';
/**
* Demo.
*/
var demo = (function(window, undefined) {
/**
* Enum of CSS selectors.
*/
var SELECTORS = {
pattern: '.pattern',
card: '.card',
cardImage: '.card__image',
cardClose: '.card__btn-close',
};
/**
* Enum of CSS classes.
*/
var CLASSES = {
patternHidden: 'pattern--hidden',
polygon: 'polygon',
polygonHidden: 'polygon--hidden',
overlayHidden: 'overlay--hidden',
};
/**
* Map of svg paths and points.
*/
var polygonMap = {
paths: null,
points: null
};
/**
* Container of Card instances.
*/
var layout = {};
/**
* Initialise demo.
*/
function init() {
// For options see: https://github.com/qrohlf/Trianglify
var pattern = Trianglify({
width: window.innerWidth,
height: window.innerHeight,
cell_size: 90,
variance: 1,
stroke_width: 0.6,
color_function : function(x, y) {
return '#f0f3f5';
}
}).svg(); // Render as SVG.
_mapPolygons(pattern);
_bindCards();
$(document.getElementsByClassName("card__overlay")).addClass(CLASSES.overlayHidden);
};
/**
* Store path elements, map coordinates and sizes.
* #param {Element} pattern The SVG Element generated with Trianglify.
* #private
*/
function _mapPolygons(pattern) {
// Append SVG to pattern container.
$(SELECTORS.pattern).append(pattern);
// Convert nodelist to array,
// Used `.childNodes` because IE doesn't support `.children` on SVG.
polygonMap.paths = [].slice.call(pattern.childNodes);
polygonMap.points = [];
polygonMap.paths.forEach(function(polygon) {
// Hide polygons by adding CSS classes to each svg path (used attrs because of IE).
$(polygon).attr('class', CLASSES.polygon + ' ' + CLASSES.polygonHidden);
var rect = polygon.getBoundingClientRect();
var point = {
x: rect.left + rect.width / 2,
y: rect.top + rect.height / 2
};
polygonMap.points.push(point);
});
// All polygons are hidden now, display the pattern container.
$(SELECTORS.pattern).removeClass(CLASSES.patternHidden);
};
/**
* Bind Card elements.
* #private
*/
function _bindCards() {
var elements = $(SELECTORS.card);
$.each(elements, function(card, i) {
var instance = new Card(i, card);
layout[i] = {
card: instance
};
var cardImage = $(card).find(SELECTORS.cardImage);
var cardClose = $(card).find(SELECTORS.cardClose);
$(cardImage).on('click', _playSequence.bind(this, true, i));
$(cardClose).on('click', _playSequence.bind(this, false, i));
});
};
/**
* Create a sequence for the open or close animation and play.
* #param {boolean} isOpenClick Flag to detect when it's a click to open.
* #param {number} id The id of the clicked card.
* #param {Event} e The event object.
* #private
*
*/
function _playSequence(isOpenClick, id, e) {
var card = layout[id].card;
// Prevent when card already open and user click on image.
if (card.isOpen && isOpenClick) return;
// Create timeline for the whole sequence.
var sequence = new TimelineLite({paused: true});
var tweenOtherCards = _showHideOtherCards(id);
if (!card.isOpen) {
// Open sequence.
sequence.add(tweenOtherCards);
sequence.add(card.openCard(_onCardMove), 0);
} else {
// Close sequence.
var closeCard = card.closeCard();
var position = closeCard.duration() * 0.8; // 80% of close card tween.
sequence.add(closeCard);
sequence.add(tweenOtherCards, position);
}
sequence.play();
};
/**
* Show/Hide all other cards.
* #param {number} id The id of the clcked card to be avoided.
* #private
*/
function _showHideOtherCards(id) {
var TL = new TimelineLite;
var selectedCard = layout[id].card;
for (var i in layout) {
var card = layout[i].card;
// When called with `openCard`.
if (card.id !== id && !selectedCard.isOpen) {
TL.add(card.hideCard(), 0);
}
// When called with `closeCard`.
if (card.id !== id && selectedCard.isOpen) {
TL.add(card.showCard(), 0);
}
}
return TL;
};
/**
* Callback to be executed on Tween update, whatever a polygon
* falls into a circular area defined by the card width the path's
* CSS class will change accordingly.
* #param {Object} track The card sizes and position during the floating.
* #private
*/
function _onCardMove(track) {
var radius = track.width / 2;
var center = {
x: track.x,
y: track.y
};
polygonMap.points.forEach(function(point, i) {
if (_detectPointInCircle(point, radius, center)) {
$(polygonMap.paths[i]).attr('class', CLASSES.polygon);
} else {
$(polygonMap.paths[i]).attr('class', CLASSES.polygon + ' ' + CLASSES.polygonHidden);
}
});
}
/**
* Detect if a point is inside a circle area.
* #private
*/
function _detectPointInCircle(point, radius, center) {
var xp = point.x;
var yp = point.y;
var xc = center.x;
var yc = center.y;
var d = radius * radius;
var isInside = Math.pow(xp - xc, 2) + Math.pow(yp - yc, 2) <= d;
return isInside;
};
// Expose methods.
return {
init: init
};
})(window);
// Kickstart Demo.
window.onload = demo.init;
)
I'm essentially adding a class which should completely hide a div element, for which I have overkilled due in part to major frustrations:
.overlay--hidden {
display: none !important;
overflow: hidden !important;
opacity: 0 !important;
}
So this should completely kill the card__overlay div. I have added the same css "stylings" within the inspector and it works as should.
<div class="card__content card__overlay">
<div class="card__caption">
<p class="card__subtitle overlay">NEWS</p>
</div>
</div>
My issue is that essentially the getElementsByClassName method in the init function doesn't seem to be working - the class I want which is essentially hiding the div is not being added!
My question is related to this older thread Google Maps v3 setting a circle with editable radius but not center
I had the same issue like this guy and solved my problem with the solution of geocodezip.
But now no stucked at another point. I have a checkbox called byRadius and only if this checkbox is marked I would like to show the circle. If not, I only want to show the marker. But I'm not able to find the circle or the marker in my HTML, so I'm not sure how I can change this visibility of the circle.
I used a listener on my checkbox to listen to the change-event and when fired i would like to change the visibility.
$scope.$watch('vm.socialMediaFilter.byRadius', function ()
{
var circle = document.getElementById('circleModal');
console.log(vm.socialMediaFilter.byRadius);
});
HTML-Tag of map
<div class="col-xs-12">
<ng-map id="modalMap" class="gov-map" style="display: block; height: 25vh;"</ng-map>
</div>
what got rendered from controller + html
(function () {
'use strict';
angular
.module('kokosGisApp')
.controller('SocialMediaFilterDialogController', SocialMediaFilterDialogController);
SocialMediaFilterDialogController.$inject = ['Principal', '$timeout', '$rootScope', '$scope', '$stateParams', '$uibModalInstance', 'entity', 'SocialMediaFilter', 'User', 'NgMap'];
function SocialMediaFilterDialogController(Principal, $timeout, $rootScope, $scope, $stateParams, $uibModalInstance, entity, SocialMediaFilter, User, NgMap) {
var vm = this;
vm.socialMediaFilter = entity;
vm.clear = clear;
vm.save = save;
vm.users = User.query();
if (vm.socialMediaFilter.mapInit == 0) {
vm.socialMediaFilter.type = "Facebook";
vm.socialMediaFilter.radius = 150;
vm.socialMediaFilter.address = $rootScope.res[0];
vm.socialMediaFilter.city = $rootScope.res[1];
vm.socialMediaFilter.latitude = $rootScope.mouseEventLatLng.lat();
vm.socialMediaFilter.longitude = $rootScope.mouseEventLatLng.lng();
} else if (vm.socialMediaFilter.mapInit == 1) {
vm.socialMediaFilter.type = "Facebook";
vm.socialMediaFilter.radius = 150;
vm.socialMediaFilter.address = '';
vm.socialMediaFilter.city = '';
vm.socialMediaFilter.latitude = 50.875863;
vm.socialMediaFilter.longitude = 8.0168847;
}
Principal.identity().then(function (user) {
vm.socialMediaFilter.user = user;
});
$timeout(function () {
angular.element('.form-group:eq(1)>input').focus();
});
function clear() {
$uibModalInstance.dismiss('cancel');
}
function save() {
NgMap.getMap({id: 'modalMap'}).then(function (map) {
if (typeof this.map.shapes !== 'undefined') {
vm.socialMediaFilter.latitude = map.shapes.circleModal.center.lat();
vm.socialMediaFilter.longitude = map.shapes.circleModal.center.lng();
vm.socialMediaFilter.radius = map.shapes.circleModal.radius;
} else {
vm.socialMediaFilter.latitude = map.markers.markerModal.position.lat();
vm.socialMediaFilter.longitude = map.markers.markerModal.position.lng();
vm.socialMediaFilter.radius = null;
}
});
vm.isSaving = true;
if (vm.socialMediaFilter.id !== null) {
SocialMediaFilter.update(vm.socialMediaFilter, onSaveSuccess, onSaveError);
} else {
SocialMediaFilter.save(vm.socialMediaFilter, onSaveSuccess, onSaveError);
}
}
function onSaveSuccess(result) {
$scope.$emit('kokosGisApp:socialMediaFilterUpdate', result);
$uibModalInstance.close(result);
vm.isSaving = false;
}
function onSaveError() {
vm.isSaving = false;
}
$scope.$watch('vm.socialMediaFilter.byRadius', function ()
{
if (vm.socialMediaFilter.byRadius) {
$scope.circle.visible = false;
} else $scope.circle.visible = true;
});
/**
* A distance widget that will display a circle that can be resized and will
* provide the radius in km.
*
* #param {google.maps.Map} map The map to attach to.
*
* #constructor
*/
function DistanceWidget(map) {
this.set('map', map);
this.set('position', map.getCenter());
var marker = new google.maps.Marker({
// draggable: true, // <-- change to make so position doesn't move
title: 'Move me!',
id: 'markerModal'
});
// Bind the marker map property to the DistanceWidget map property
marker.bindTo('map', this);
// Bind the marker position property to the DistanceWidget position
// property
marker.bindTo('position', this);
// Create a new radius widget
$scope.radiusWidget = new RadiusWidget();
// Bind the radiusWidget map to the DistanceWidget map
$scope.radiusWidget.bindTo('map', this);
// Bind the radiusWidget center to the DistanceWidget position
$scope.radiusWidget.bindTo('center', this, 'position');
// Bind to the radiusWidgets' distance property
this.bindTo('distance', $scope.radiusWidget);
// Bind to the radiusWidgets' bounds property
this.bindTo('bounds', $scope.radiusWidget);
}
DistanceWidget.prototype = new google.maps.MVCObject();
/**
* A radius widget that add a circle to a map and centers on a marker.
*
* #constructor
*/
function RadiusWidget() {
$scope.circle = new google.maps.Circle({
strokeWeight: 2,
color: 'orange',
visible: true
});
// Set the distance property value, default to 10km.
this.set('distance', 150);
// Bind the RadiusWidget bounds property to the circle bounds property.
this.bindTo('bounds', $scope.circle);
// Bind the circle center to the RadiusWidget center property
$scope.circle.bindTo('center', this);
// Bind the circle map to the RadiusWidget map
$scope.circle.bindTo('map', this);
// Bind the circle radius property to the RadiusWidget radius property
$scope.circle.bindTo('radius', this);
// this.bindTo('byRadius', vm.socialMediaFilter.byRadius);
// Add the sizer marker
this.addSizer_();
}
RadiusWidget.prototype = new google.maps.MVCObject();
// RadiusWidget.prototype.byRadius_changed = function () {
// console.log('dasdas');
// }
/**
* Update the radius when the distance has changed.
*/
RadiusWidget.prototype.distance_changed = function () {
this.set('radius', this.get('distance'));
};
/**
* Add the sizer marker to the map.
*
* #private
*/
RadiusWidget.prototype.addSizer_ = function () {
var sizer = new google.maps.Marker({
draggable: true,
visible: true
});
sizer.bindTo('map', this);
sizer.bindTo('position', this, 'sizer_position');
var me = this;
google.maps.event.addListener(sizer, 'drag', function () {
// As the sizer is being dragged, its position changes. Because the
// RadiusWidget's sizer_position is bound to the sizer's position, it will
// change as well.
var min = 0.5;
var max = 2500;
var pos = me.get('sizer_position');
var center = me.get('center');
var distance = google.maps.geometry.spherical.computeDistanceBetween(center, pos);
if (distance < min) {
me.set('sizer_position', google.maps.geometry.spherical.computeOffset(center, min, google.maps.geometry.spherical.computeHeading(center, pos)));
} else if (distance > max) {
me.set('sizer_position', google.maps.geometry.spherical.computeOffset(center, max, google.maps.geometry.spherical.computeHeading(center, pos)));
}
// Set the circle distance (radius)
me.setDistance();
});
};
/**
* Update the center of the circle and position the sizer back on the line.
*
* Position is bound to the DistanceWidget so this is expected to change when
* the position of the distance widget is changed.
*/
RadiusWidget.prototype.center_changed = function () {
var bounds = this.get('bounds');
// Bounds might not always be set so check that it exists first.
if (bounds) {
var lng = bounds.getNorthEast().lng();
// Put the sizer at center, right on the circle.
var position = new google.maps.LatLng(this.get('center').lat(), lng);
this.set('sizer_position', position);
}
};
/**
* Set the distance of the circle based on the position of the sizer.
*/
RadiusWidget.prototype.setDistance = function () {
// As the sizer is being dragged, its position changes. Because the
// RadiusWidget's sizer_position is bound to the sizer's position, it will
// change as well.
var pos = this.get('sizer_position');
var center = this.get('center');
var distance = google.maps.geometry.spherical.computeDistanceBetween(center, pos);
// Set the distance property for any objects that are bound to it
this.set('distance', distance);
};
function initMap() {
NgMap.getMap({id:'modalMap'}).then(function (map) {
map.setCenter(new google.maps.LatLng(vm.socialMediaFilter.latitude, vm.socialMediaFilter.longitude));
var currentCenter = map.getCenter();
map.setZoom(15);
google.maps.event.trigger(map, 'resize');
map.setCenter(currentCenter);
var distanceWidget = new DistanceWidget(map);
// google.maps.event.addListener(distanceWidget, 'distance_changed', function () {
// displayInfo(distanceWidget);
// });
//
// google.maps.event.addListener(distanceWidget, 'position_changed', function () {
// displayInfo(distanceWidget);
// });
}
);
}
google.maps.event.addDomListener(window, 'load', initMap());
}
})();
this is the controller that is used as a modal for saving an entity to my db
And i only would like to show the circle if the checkbox "Suchradius eingrenzen" is marked. If not marked I only would like to show the marker in the center.
modal
I need help only having the anchors for rotating. Right now there is five anchors and I don't know how to get rid of all of them except the rotate one. I would also only like the anchors to show when the user hovers over the image
Here is my code
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
</head>
<body>
<body onmousedown="return false;">
<div id="container"></div>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.7.4.min.js">
</script>
<script>
function update(activeAnchor) {
var group = activeAnchor.getParent();
var topLeft = group.get('.topLeft')[0];
var topRight = group.get('.topRight')[0];
var bottomRight = group.get('.bottomRight')[0];
var bottomLeft = group.get('.bottomLeft')[0];
var rotateAnchor = group.get('.rotateAnchor')[0];
var image = group.get('Image')[0];
var anchorX = activeAnchor.getX();
var anchorY = activeAnchor.getY();
var imageWidth = image.getWidth();
var imageHeight = image.getHeight();
var offsetX = Math.abs((topLeft.getX() + bottomRight.getX() + 10) / 2);
var offsetY = Math.abs((topLeft.getY() + bottomRight.getY() + 10) / 2);
// update anchor positions
switch (activeAnchor.getName()) {
case 'rotateAnchor':
group.setOffset(offsetX, offsetY);
break;
case 'topLeft':
topRight.setY(anchorY);
bottomLeft.setX(anchorX);
break;
case 'topRight':
topLeft.setY(anchorY);
bottomRight.setX(anchorX);
break;
case 'bottomRight':
topRight.setX(anchorX);
bottomLeft.setY(anchorY);
break;
case 'bottomLeft':
topLeft.setX(anchorX);
bottomRight.setY(anchorY);
break;
}
rotateAnchor.setX(topRight.getX() + 5);
rotateAnchor.setY(topRight.getY() + 20);
image.setPosition((topLeft.getPosition().x + 20), (topLeft.getPosition().y + 20));
var width = topRight.getX() - topLeft.getX() - 30;
var height = bottomLeft.getY() - topLeft.getY() - 30;
if (width && height) {
image.setSize(width, height);
}
}
function addAnchor(group, x, y, name, dragBound) {
var stage = group.getStage();
var layer = group.getLayer();
var anchor = new Kinetic.Circle({
x: x,
y: y,
stroke: '#666',
fill: '#ddd',
strokeWidth: 2,
radius: 8,
name: name,
draggable: true,
dragOnTop: false
});
if (dragBound == 'rotate') {
anchor.setAttrs({
dragBoundFunc: function (pos) {
return getRotatingAnchorBounds(pos, group);
}
});
}
anchor.on('dragmove', function() {
update(this);
layer.draw();
});
anchor.on('mousedown touchstart', function() {
group.setDraggable(false);
this.moveToTop();
});
anchor.on('dragend', function() {
group.setDraggable(true);
layer.draw();
});
// add hover styling
anchor.on('mouseover', function() {
var layer = this.getLayer();
document.body.style.cursor = 'pointer';
this.setStrokeWidth(4);
layer.draw();
});
anchor.on('mouseout', function() {
var layer = this.getLayer();
document.body.style.cursor = 'default';
this.setStrokeWidth(2);
layer.draw();
});
group.add(anchor);
}
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
function getRotatingAnchorBounds(pos, group) {
var topLeft = group.get('.topLeft')[0];
var bottomRight = group.get('.bottomRight')[0];
var topRight = group.get('.topRight')[0];
var absCenterX = Math.abs((topLeft.getAbsolutePosition().x + 5 + bottomRight.getAbsolutePosition().x + 5) / 2);
var absCenterY = Math.abs((topLeft.getAbsolutePosition().y + 5 + bottomRight.getAbsolutePosition().y + 5) / 2);
var relCenterX = Math.abs((topLeft.getX() + bottomRight.getX()) / 2);
var relCenterY = Math.abs((topLeft.getY() + bottomRight.getY()) / 2);
var radius = distance(relCenterX, relCenterY, topRight.getX() + 5, topRight.getY() + 20);
var scale = radius / distance(pos.x, pos.y, absCenterX, absCenterY);
var realRotation = Math.round(degrees(angle(relCenterX, relCenterY, topRight.getX() + 5, topRight.getY() + 20)));
var rotation = Math.round(degrees(angle(absCenterX, absCenterY, pos.x, pos.y)));
rotation -= realRotation;
group.setRotationDeg(rotation);
return {
y: Math.round((pos.y - absCenterY) * scale + absCenterY),
x: Math.round((pos.x - absCenterX) * scale + absCenterX)
};
}
function radians(degrees) { return degrees * (Math.PI / 180); }
function degrees(radians) { return radians * (180 / Math.PI); }
// Calculate the angle between two points.
function angle(cx, cy, px, py) {
var x = cx - px;
var y = cy - py;
return Math.atan2(-y, -x);
}
// Calculate the distance between two points.
function distance(p1x, p1y, p2x, p2y) {
return Math.sqrt(Math.pow((p2x - p1x), 2) + Math.pow((p2y - p1y), 2));
}
function initStage(images) {
var stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 400
});
var darthVaderGroup = new Kinetic.Group({
x: 270,
y: 100,
draggable: true
});
var yodaGroup = new Kinetic.Group({
x: 100,
y: 110,
draggable: true
});
var layer = new Kinetic.Layer();
/*
* go ahead and add the groups
* to the layer and the layer to the
* stage so that the groups have knowledge
* of its layer and stage
*/
layer.add(darthVaderGroup);
layer.add(yodaGroup);
stage.add(layer);
// darth vader
var darthVaderImg = new Kinetic.Image({
x: 0,
y: 0,
image: images.darthVader,
width: 200,
height: 138,
name: 'image'
});
darthVaderGroup.add(darthVaderImg);
addAnchor(darthVaderGroup, -20, -20, 'topLeft', 'none');
addAnchor(darthVaderGroup, 220, -20, 'topRight', 'none');
addAnchor(darthVaderGroup, 220, 158, 'bottomRight', 'none');
addAnchor(darthVaderGroup, -20, 158, 'bottomLeft','none');
addAnchor(darthVaderGroup, 225, 0, 'rotateAnchor','rotate');
darthVaderGroup.on('dragstart', function() {
this.moveToTop();
});
stage.draw();
}
var sources = {
darthVader: 'http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg'
};
loadImages(sources, initStage);
</script>
</body>
</html>
You can use each anchors show/hide methods inside the images mouseenter/mouseleave events to display the anchors when the mouse enters the image:
image.on("mouseleave",function(){ anchor1.hide(); }
image.on("mouseenter",function(){ anchor1.show(); layer.draw(); }
Problem is that since your anchors are partly outside your image, so hiding the anchors when the mouse leaves the image might make the anchors "disappear" when the user intends to use them.
The ideal solution would be to listen for mouseenter/mouseleave events on the group which contains the image but also extends to include the outside part of the anchors. Unfortunately, a Kinetic.Group will not respond to mouseenter/mouseleave events.
A workaround is to create a Kinetic.Rect background to the group which includes the images plus the anchors. The rect will listen for mouseenter/mouseleave events and will show/hide the anchors. If you don't want the background rect to be visible, just set it's opacity to .001. The rect will still listen for events, but will be invisible.
groupBackgroundRect.on("mouseleave",function(){ anchor1.hide(); }
groupBackgroundRect.on("mouseenter",function(){ anchor1.show(); layer.draw(); }
A related note:
With KineticJS, combining rotation with resizing is made more difficult than it needs to be because KineticJS uses offsetX/offsetY as both an object's rotation point and as an offset to its position. Your key to making it work will be to re-center the offset point after resizing so that your rotation takes place around the new centerpoint--not the previous centerpoint. (or reset the offset reference point to any other point that you want to rotate around).
I'm trying to implement the 'Smart Info Window' code by Pamela Fox into my Google map and I just can't seem to get this to work. The map appears with the markers correctly positioned on it. But when I click on the marker nothing happens, but I don't get any error messages and I must admit to being a bit lost with this.
Where am I going wrong?
HTML Form
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>All Locations</title>
<link rel="stylesheet" href="css/alllocationsstyle.css" type="text/css" media="all" />
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false&language=en"></script>
<script type="text/javascript" src="js/smartinfowindow.js"></script>
<script type="text/javascript">
var customIcons = {
0: {
icon: 'http://labs.google.com/ridefinder/images/mm_20_red.png',
shadow: 'http://labs.google.com/ridefinder/images/mm_20_shadow.png'
},
1: {
icon: 'http://labs.google.com/ridefinder/images/mm_20_green.png',
shadow: 'http://labs.google.com/ridefinder/images/mm_20_shadow.png'
}
};
function load() {
var map = new google.maps.Map(document.getElementById("map"), {
center: new google.maps.LatLng(54.312195845815246,-4.45948481875007),
zoom:6,
mapTypeId: 'roadmap'
});
downloadUrl("phpfile.php", function(data) {
var xml = data.responseXML;
var markers = xml.documentElement.getElementsByTagName("marker");
var bounds = new google.maps.LatLngBounds();
for (var i = 0; i < markers.length; i++) {
var locationname = markers[i].getAttribute("locationname");
var address = markers[i].getAttribute("address");
var totalfinds = markers[i].getAttribute("totalfinds");
var point = new google.maps.LatLng(
parseFloat(markers[i].getAttribute("osgb36lat")),
parseFloat(markers[i].getAttribute("osgb36lon")));
var html = locationname + "<p>" + 'No. of finds: ' + "<b>" + totalfinds + "</b>" + "</p>";
var icon = {};
if (totalfinds == 0) {
icon = customIcons[0];
} else if (totalfinds >= 1) {
icon = customIcons[1];
}
var marker = new google.maps.Marker({
map: map,
position: point,
title: address,
icon: icon.icon,
shadow: icon.shadow
});
bounds.extend(point);
map.fitBounds(bounds);
google.maps.event.addListener(point, 'click', function(e) {
var infobox = new SmartInfoWindow({position: point, map: map, html: html});
});
}
});
}
function downloadUrl(url, callback) {
var request = window.ActiveXObject ?
new ActiveXObject('Microsoft.XMLHTTP') :
new XMLHttpRequest;
request.onreadystatechange = function() {
if (request.readyState == 4) {
request.onreadystatechange = doNothing;
callback(request, request.status);
}
};
request.open('GET', url, true);
request.send(null);
}
function doNothing() {}
</script>
</head>
<body onLoad="load()">
<div id="map"></div>
</body>
</html>
Smart Window JS
/* An SmartInfoWindow is like an info window, but it displays
* under the marker, opens quicker, and has flexible styling.
* #param {Object} opts Passes configuration options.
*/
function SmartInfoWindow(opts) {
google.maps.OverlayView.call(this);
this.latlng_ = opts.position;
this.content_ = opts.content;
this.map_ = opts.map;
this.height_ = 351;
this.width_ = 280;
this.size_ = new google.maps.Size(this.height_, this.width_);
this.offsetVertical_ = -this.height_;
this.offsetHorizontal_ = 0;
this.panned_ = false;
this.setMap(this.map_);
// We need to listen to bounds_changed event so that we can redraw
// absolute position every time the map moves.
// This is only needed because we append to body instead of map panes.
var me = this;
google.maps.event.addListener(this.map_, 'bounds_changed', function() {
me.draw();
});
}
/**
* SmartInfoWindow extends GOverlay class from the Google Maps API
*/
SmartInfoWindow.prototype = new google.maps.OverlayView();
/**
* Creates the DIV representing this SmartInfoWindow
*/
SmartInfoWindow.prototype.onRemove = function() {
if (this.div_) {
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
}
};
/**
* Called when the overlay is first added to the map.
*/
SmartInfoWindow.prototype.onAdd = function() {
// Creates the element if it doesn't exist already.
this.createElement();
};
/**
* Redraw based on the current projection and zoom level.
*/
SmartInfoWindow.prototype.draw = function() {
// Since we use bounds changed listener, projection is sometimes null
if (!this.getProjection()) {
return;
}
// This gives us the position in the tiles div.
var pixPosition = this.getProjection().fromLatLngToDivPixel(this.latlng_);
var centerPosition = this.getProjection().fromLatLngToDivPixel(this.map_.getCenter());
var centerPositionReal = new google.maps.Point(this.map_.getDiv().offsetWidth/2, this.map_.getDiv().offsetHeight/2);
// Figure out difference between map div and tiles div, so that we can
// calculate position in map div
var centerOffsetX = -centerPosition.x + centerPositionReal.x;
var centerOffsetY = -centerPosition.y + centerPositionReal.y;
if (!pixPosition) return;
var alignment = this.getBestAlignment();
var paddingTop = 0;
var paddingLeft = 0;
var widthLess = 0;
switch (alignment) {
case SmartInfoWindow.Align.ABOVE:
this.width_ = 280;
this.height_ = 351;
image = 'infobox_above.gif';
this.offsetX_ = -(this.width_ / 2 - 17);
this.offsetY_ = -(this.height_ + 12);
break;
case SmartInfoWindow.Align.BELOW:
this.width_ = 280;
this.height_ = 351;
image = 'infobox_below.gif';
this.offsetX_ = -(this.width_ / 2 - 17);
this.offsetY_ = -15;
paddingTop = 20;
break;
case SmartInfoWindow.Align.LEFT:
this.width_ = 307;
this.height_ = 326;
image = 'infobox_left.gif';
this.offsetX_ = -(this.width_) + 10;
this.offsetY_ = -(this.height_ / 2 + 33);
widthLess = 20;
break;
case SmartInfoWindow.Align.RIGHT:
image = 'infobox_right.gif';
this.width_ = 307;
this.height_ = 326;
this.offsetX_ = 6;
this.offsetY_ = -(this.height_ / 2 + 33);
paddingLeft = 20;
widthLess = 20;
break;
}
// Now position our DIV based on the DIV coordinates of our bounds
this.div_.style.width = this.width_ + 'px';
this.div_.style.left = (pixPosition.x + this.offsetX_) + centerOffsetX + 'px';
this.div_.style.height = this.height_ + 'px';
this.div_.style.top = (pixPosition.y + this.offsetY_) + centerOffsetY + 'px';
//this.div_.style.paddingTop = paddingTop + 'px';
//this.div_.style.paddingLeft = paddingLeft + 'px';
this.div_.style.background = 'url("images/' + image + '")';
this.div_.style.display = 'block';
this.wrapperDiv_.style.width = (this.width_- widthLess) + 'px';
this.wrapperDiv_.style.height = this.height_ + 'px';
this.wrapperDiv_.style.marginTop = paddingTop + 'px';
this.wrapperDiv_.style.marginLeft = paddingLeft + 'px';
this.wrapperDiv_.style.overflow = 'hidden';
if (!this.panned_) {
this.panned_ = true;
this.maybePanMap();
}
};
/**
* Creates the DIV representing this SmartInfoWindow in the floatPane. If the panes
* object, retrieved by calling getPanes, is null, remove the element from the
* DOM. If the div exists, but its parent is not the floatPane, move the div
* to the new pane.
* Called from within draw. Alternatively, this can be called specifically on
* a panes_changed event.
*/
SmartInfoWindow.prototype.createElement = function() {
var panes = this.getPanes();
var div = this.div_;
if (!div) {
// This does not handle changing panes. You can set the map to be null and
// then reset the map to move the div.
div = this.div_ = document.createElement('div');
div.style.border = '0px none';
div.style.position = 'absolute';
div.style.overflow = 'hidden';
var wrapperDiv = this.wrapperDiv_ = document.createElement('div');
var contentDiv = document.createElement('div');
if (typeof this.content_ == 'string') {
contentDiv.innerHTML = this.content_;
} else {
contentDiv.appendChild(this.content_);
}
var topDiv = document.createElement('div');
topDiv.style.textAlign = 'right';
var closeImg = document.createElement('img');
closeImg.src = 'images/closebigger.gif';
closeImg.style.width = '32px';
closeImg.style.height = '32px';
closeImg.style.cursor = 'pointer';
topDiv.appendChild(closeImg);
function removeSmartInfoWindow(ib) {
return function() {
ib.setMap(null);
};
}
google.maps.event.addDomListener(closeImg, 'click', removeSmartInfoWindow(this));
wrapperDiv.appendChild(topDiv);
wrapperDiv.appendChild(contentDiv);
div.appendChild(wrapperDiv);
div.style.display = 'none';
// Append to body, to avoid bug with Webkit browsers
// attempting CSS transforms on IFRAME or SWF objects
// and rendering badly.
document.body.appendChild(div);
} else if (div.parentNode != panes.floatPane) {
// The panes have changed. Move the div.
div.parentNode.removeChild(div);
panes.floatPane.appendChild(div);
} else {
// The panes have not changed, so no need to create or move the div.
}
};
SmartInfoWindow.mouseFilter = function(e) {
e.returnValue = 'true';
e['handled'] = true;
}
/**
* Closes infowindow
*/
SmartInfoWindow.prototype.close = function() {
this.setMap(null);
};
/**
* Pan the map to fit the SmartInfoWindow,
* if its top or bottom corners aren't visible.
*/
SmartInfoWindow.prototype.maybePanMap = function() {
// if we go beyond map, pan map
var map = this.map_;
var projection = this.getProjection();
var bounds = map.getBounds();
if (!bounds) return;
// The dimension of the infowindow
var iwWidth = this.width_;
var iwHeight = this.height_;
// The offset position of the infowindow
var iwOffsetX = this.offsetX_;
var iwOffsetY = this.offsetY_;
var anchorPixel = projection.fromLatLngToDivPixel(this.latlng_);
var bl = new google.maps.Point(anchorPixel.x + iwOffsetX + 20,
anchorPixel.y + iwOffsetY + iwHeight);
var tr = new google.maps.Point(anchorPixel.x + iwOffsetX + iwWidth,
anchorPixel.y + iwOffsetY);
var sw = projection.fromDivPixelToLatLng(bl);
var ne = projection.fromDivPixelToLatLng(tr);
// The bounds of the infowindow
if (!map.getBounds().contains(ne) || !map.getBounds().contains(sw)) {
map.panToBounds(new google.maps.LatLngBounds(sw, ne));
}
};
/**
* #enum {number}
*/
SmartInfoWindow.Align = {
ABOVE: 0,
LEFT: 1,
RIGHT: 2,
BELOW: 3
};
/**
* Finds best alignment for infowindow.
* #return {number} Alignment.
*/
SmartInfoWindow.prototype.getBestAlignment = function() {
var bestAlignment = SmartInfoWindow.Align.LEFT;
var minPan = 0;
for (var alignment in SmartInfoWindow.Align) {
var alignment = SmartInfoWindow.Align[alignment];
var panValue = this.getPanValue(alignment);
if (panValue > minPan) {
minPan = panValue;
bestAlignment = alignment;
}
}
return bestAlignment;
};
/**
* Calculates distance of corner for each alignment.
* #param {number} alignment An alignment constant.
* #return {number} Distance for that alignment.
*/
SmartInfoWindow.prototype.getPanValue = function(alignment) {
var mapSize = new google.maps.Size(this.map_.getDiv().offsetWidth,
this.map_.getDiv().offsetHeight);
var bounds = this.map_.getBounds();
var sideLatLng;
switch (alignment) {
case SmartInfoWindow.Align.ABOVE:
sideLatLng = new google.maps.LatLng(bounds.getNorthEast().lat(),
this.latlng_.lng());
break;
case SmartInfoWindow.Align.BELOW:
sideLatLng = new google.maps.LatLng(bounds.getSouthWest().lat(),
this.latlng_.lng());
break;
case SmartInfoWindow.Align.RIGHT:
sideLatLng = new google.maps.LatLng(this.latlng_.lat(),
bounds.getNorthEast().lng());
break;
case SmartInfoWindow.Align.LEFT:
sideLatLng = new google.maps.LatLng(this.latlng_.lat(),
bounds.getSouthWest().lng());
break;
}
var dist = SmartInfoWindow.distHaversine(this.latlng_.lat(), this.latlng_.lng(),
sideLatLng.lat(), sideLatLng.lng());
return dist;
};
/**
* Converts degrees to radians.
* #param {number} num Angle in degrees.
* #return {number} Angle in radians.
*/
SmartInfoWindow.toRad = function(num) {
return num * Math.PI / 180;
}
/**
* Calculates distance between two coordinates.
* #param {number} lat1 Latitude of first coord.
* #param {number} lon1 Longitude of second coord.
* #param {number} lat2 Latitude of second coord.
* #param {number} lon2 Longitude of second coord.
* #return {number} The distance.
*/
SmartInfoWindow.distHaversine = function(lat1, lon1, lat2, lon2) {
var R = 6371; // earth's mean radius in km
var dLat = SmartInfoWindow.toRad(lat2 - lat1);
var dLon = SmartInfoWindow.toRad(lon2 - lon1);
lat1 = SmartInfoWindow.toRad(lat1), lat2 = SmartInfoWindow.toRad(lat2);
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(lat1) * Math.cos(lat2) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = R * c;
return d;
}
UPDATED CODE
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Locations</title>
<link rel="stylesheet" href="css/alllocationsstyle.css" type="text/css" media="all" />
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false&language=en"></script>
<script type="text/javascript">
function InfoBox(opts) {
google.maps.OverlayView.call(this);
this.latlng_ = opts.latlng;
this.map_ = opts.map;
this.offsetVertical_ = -195;
this.offsetHorizontal_ = 0;
this.height_ = 165;
this.width_ = 266;
var me = this;
this.boundsChangedListener_ =
google.maps.event.addListener(this.map_, "bounds_changed", function() {
return me.panMap.apply(me);
});
// Once the properties of this OverlayView are initialized, set its map so
// that we can display it. This will trigger calls to panes_changed and
// draw.
this.setMap(this.map_);
}
/* InfoBox extends GOverlay class from the Google Maps API
*/
InfoBox.prototype = new google.maps.OverlayView();
/* Creates the DIV representing this InfoBox
*/
InfoBox.prototype.remove = function() {
if (this.div_) {
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
}
};
/* Redraw the Bar based on the current projection and zoom level
*/
InfoBox.prototype.draw = function() {
// Creates the element if it doesn't exist already.
this.createElement();
if (!this.div_) return;
// Calculate the DIV coordinates of two opposite corners of our bounds to
// get the size and position of our Bar
var pixPosition = this.getProjection().fromLatLngToDivPixel(this.latlng_);
if (!pixPosition) return;
// Now position our DIV based on the DIV coordinates of our bounds
this.div_.style.width = this.width_ + "px";
this.div_.style.left = (pixPosition.x + this.offsetHorizontal_) + "px";
this.div_.style.height = this.height_ + "px";
this.div_.style.top = (pixPosition.y + this.offsetVertical_) + "px";
this.div_.style.display = 'block';
};
/* Creates the DIV representing this InfoBox in the floatPane. If the panes
* object, retrieved by calling getPanes, is null, remove the element from the
* DOM. If the div exists, but its parent is not the floatPane, move the div
* to the new pane.
* Called from within draw. Alternatively, this can be called specifically on
* a panes_changed event.
*/
InfoBox.prototype.createElement = function() {
var panes = this.getPanes();
var div = this.div_;
if (!div) {
// This does not handle changing panes. You can set the map to be null and
// then reset the map to move the div.
div = this.div_ = document.createElement("div");
div.style.border = "0px none";
div.style.position = "absolute";
div.style.background = "url('http://gmaps-samples.googlecode.com/svn/trunk/images/blueinfowindow.gif')";
div.style.width = this.width_ + "px";
div.style.height = this.height_ + "px";
var contentDiv = document.createElement("div");
contentDiv.style.padding = "30px"
contentDiv.innerHTML = "<b>Hello World!</b>";
var topDiv = document.createElement("div");
topDiv.style.textAlign = "right";
var closeImg = document.createElement("img");
closeImg.style.width = "32px";
closeImg.style.height = "32px";
closeImg.style.cursor = "pointer";
closeImg.src = "http://gmaps-samples.googlecode.com/svn/trunk/images/closebigger.gif";
topDiv.appendChild(closeImg);
function removeInfoBox(ib) {
return function() {
ib.setMap(null);
};
}
google.maps.event.addDomListener(closeImg, 'click', removeInfoBox(this));
div.appendChild(topDiv);
div.appendChild(contentDiv);
div.style.display = 'none';
panes.floatPane.appendChild(div);
this.panMap();
} else if (div.parentNode != panes.floatPane) {
// The panes have changed. Move the div.
div.parentNode.removeChild(div);
panes.floatPane.appendChild(div);
} else {
// The panes have not changed, so no need to create or move the div.
}
}
/* Pan the map to fit the InfoBox.
*/
InfoBox.prototype.panMap = function() {
// if we go beyond map, pan map
var map = this.map_;
var bounds = map.getBounds();
if (!bounds) return;
// The position of the infowindow
var position = this.latlng_;
// The dimension of the infowindow
var iwWidth = this.width_;
var iwHeight = this.height_;
// The offset position of the infowindow
var iwOffsetX = this.offsetHorizontal_;
var iwOffsetY = this.offsetVertical_;
// Padding on the infowindow
var padX = 40;
var padY = 40;
// The degrees per pixel
var mapDiv = map.getDiv();
var mapWidth = mapDiv.offsetWidth;
var mapHeight = mapDiv.offsetHeight;
var boundsSpan = bounds.toSpan();
var longSpan = boundsSpan.lng();
var latSpan = boundsSpan.lat();
var degPixelX = longSpan / mapWidth;
var degPixelY = latSpan / mapHeight;
// The bounds of the map
var mapWestLng = bounds.getSouthWest().lng();
var mapEastLng = bounds.getNorthEast().lng();
var mapNorthLat = bounds.getNorthEast().lat();
var mapSouthLat = bounds.getSouthWest().lat();
// The bounds of the infowindow
var iwWestLng = position.lng() + (iwOffsetX - padX) * degPixelX;
var iwEastLng = position.lng() + (iwOffsetX + iwWidth + padX) * degPixelX;
var iwNorthLat = position.lat() - (iwOffsetY - padY) * degPixelY;
var iwSouthLat = position.lat() - (iwOffsetY + iwHeight + padY) * degPixelY;
// calculate center shift
var shiftLng =
(iwWestLng < mapWestLng ? mapWestLng - iwWestLng : 0) +
(iwEastLng > mapEastLng ? mapEastLng - iwEastLng : 0);
var shiftLat =
(iwNorthLat > mapNorthLat ? mapNorthLat - iwNorthLat : 0) +
(iwSouthLat < mapSouthLat ? mapSouthLat - iwSouthLat : 0);
// The center of the map
var center = map.getCenter();
// The new map center
var centerX = center.lng() - shiftLng;
var centerY = center.lat() - shiftLat;
// center the map to the new shifted center
map.setCenter(new google.maps.LatLng(centerY, centerX));
// Remove the listener after panning is complete.
google.maps.event.removeListener(this.boundsChangedListener_);
this.boundsChangedListener_ = null;
};
</script>
<script type="text/javascript">
var customIcons = {
0: {
icon: 'http://labs.google.com/ridefinder/images/mm_20_red.png',
shadow: 'http://labs.google.com/ridefinder/images/mm_20_shadow.png'
},
1: {
icon: 'http://labs.google.com/ridefinder/images/mm_20_green.png',
shadow: 'http://labs.google.com/ridefinder/images/mm_20_shadow.png'
}
};
function load() {
var map = new google.maps.Map(document.getElementById("map"), {
center: new google.maps.LatLng(54.312195845815246,-4.45948481875007),
zoom:6,
mapTypeId: 'roadmap'
});
// Change this depending on the name of your PHP file
downloadUrl("phpfile.php", function(data) {
var xml = data.responseXML;
var markers = xml.documentElement.getElementsByTagName("marker");
var bounds = new google.maps.LatLngBounds();
for (var i = 0; i < markers.length; i++) {
var locationname = markers[i].getAttribute("locationname");
var address = markers[i].getAttribute("address");
var totalfinds = markers[i].getAttribute("totalfinds");
var point = new google.maps.LatLng(
parseFloat(markers[i].getAttribute("osgb36lat")),
parseFloat(markers[i].getAttribute("osgb36lon")));
var html = locationname + "<p>" + 'No. of finds: ' + "<b>" + totalfinds + "</b>" + "</p>";
var icon = {};
if (totalfinds == 0) {
icon = customIcons[0];
} else if (totalfinds >= 1) {
icon = customIcons[1];
}
var marker = new google.maps.Marker({
map: map,
position: point,
title: address,
icon: icon.icon,
shadow: icon.shadow
});
google.maps.event.addListener(marker, "click", function(e) {
var infoBox = new InfoBox({latlng: marker.getPosition(), map: map});
});
bounds.extend(point);
map.fitBounds(bounds);
}
});
}
function downloadUrl(url, callback) {
var request = window.ActiveXObject ?
new ActiveXObject('Microsoft.XMLHTTP') :
new XMLHttpRequest;
request.onreadystatechange = function() {
if (request.readyState == 4) {
request.onreadystatechange = doNothing;
callback(request, request.status);
}
};
request.open('GET', url, true);
request.send(null);
}
function doNothing() {}
</script>
</head>
<body onLoad="load()">
<div id="map"></div>
</body>
</html>
If I am reading your code correctly you assign listener to point (google.maps.LatLng), while you should do it for a marker. In fragment:
google.maps.event.addListener(point, 'click', function(e) {
var infobox = new SmartInfoWindow({position: point, map: map, html: html});
});
change point to marker:
google.maps.event.addListener(marker, 'click', function(e) {
var infobox = new SmartInfoWindow({position: point, map: map, html: html});
});
How could I rotate an image (marker image) on a Google map V3?
There is an excellent example for V2 here, exactly doing what I need. But for GMap2! They do it with a rotating canvas.
Image rotating with JS / JQuery is frequently used, there are multiple answers about this. But how could I apply this to my maps image?
One mentioned approach is to have different images for different angles and to switch among them - this is NOT what I want. I do not like to have so many images, I want to rotate by code.
Remark: There are similar questions, but all for V2 and not V3 (as far I can tell). I need it for V3.
My js class for solving this problem is:
var RotateIcon = function(options){
this.options = options || {};
this.rImg = options.img || new Image();
this.rImg.src = this.rImg.src || this.options.url || '';
this.options.width = this.options.width || this.rImg.width || 52;
this.options.height = this.options.height || this.rImg.height || 60;
var canvas = document.createElement("canvas");
canvas.width = this.options.width;
canvas.height = this.options.height;
this.context = canvas.getContext("2d");
this.canvas = canvas;
};
RotateIcon.makeIcon = function(url) {
return new RotateIcon({url: url});
};
RotateIcon.prototype.setRotation = function(options){
var canvas = this.context,
angle = options.deg ? options.deg * Math.PI / 180:
options.rad,
centerX = this.options.width/2,
centerY = this.options.height/2;
canvas.clearRect(0, 0, this.options.width, this.options.height);
canvas.save();
canvas.translate(centerX, centerY);
canvas.rotate(angle);
canvas.translate(-centerX, -centerY);
canvas.drawImage(this.rImg, 0, 0);
canvas.restore();
return this;
};
RotateIcon.prototype.getUrl = function(){
return this.canvas.toDataURL('image/png');
};
Call it like this:
var marker = new google.maps.Marker({
icon: {
url: RotateIcon
.makeIcon(
'https://ru.gravatar.com/userimage/54712272/b8eb5f2d540a606f4a6c07c238a0bf40.png')
.setRotation({deg: 92})
.getUrl()
}})
See live example here http://jsfiddle.net/fe9grwdf/39/
I have found two extensions to the Google MAP V3: infobox.js and markerwithlabel.js
Both can handle an image DOM element as content, which in turn I can rotate via the jQuery image rotate plugin.
This even works without setting the marker's image again after rotation.
Edit: As of questions / comments below:
The extension for label is required, because it can handle other DOM elements. So I can add arbitrary HTML as label, in my particular case I add the image. And then I do rotate this image (child of the label) with the rotate plugin. So assign the image an id in order to easily access it. Actually I am using one label just for the image, and another for descriptive text.
Edit 2: Due to Stephan's comment on the DOM readiness
In my code I have found the following lines. This shows that I force a draw on the label before rotating the image.
if (!this._drawn) myImageLabel.draw(); // 1st time force a draw, otherwise rotating the image will fail because an asynchronously drawn object has not all tags in place
if (this.heading != 0) this.rotateImage(this.heading, true);
Edit 3: Code example how to create the Infobox.js
this._img = document.createElement('img');
... further manipulations of _img / Size / Id / ...
var planeImageLabelOptions = {
content: this._img,
disableAutoPan: true,
boxStyle: planeImageLabelBoxStyle,
pixelOffset: new google.maps.Size(-imgOffsetW / 2, -imgOffsetH / 2),
closeBoxURL: "",
position: latlng,
zIndex: this.altitude < 0 ? 100 : this.altitude
};
var planeImageLabel = new InfoBox(planeImageLabelOptions);
I also had a hard time to figure out the way to rotate .png marker.
I solved it like below. You can create many markers with same custom image and
rotate a specific marker you want to rotate.
I hope it helpful to you.
var id = 'my_marker_01';
var img_url = "../img/car.png";
var my_icon = img_url + "#" + id;
var marker = new google.maps.Marker({
icon: my_icon,
...
});
var rotate = 45;
$(`img[src="${my_icon}"]`).css(
{'-webkit-transform' : 'rotate('+ rotate +'deg)',
'-moz-transform' : 'rotate('+ rotate +'deg)',
'-ms-transform' : 'rotate('+ rotate +'deg)',
'transform' : 'rotate('+ rotate +'deg)'});
How could I rotate an image (marker image) on a Google map V3?
I had the same problem and I solved it with the next code:
var gmap;
NgMap.getMap(function(map){
gmap = map;
});
I suppose that you have a variable with the icon, for example:
var imagePath = 'img/customMarker.png';
First, we need to create our marker options:
var markerOptions = {
location: [x, y],
title:'some text',
draggable: true,
.
.
.
icon: imagePath
};
Let's create a marker:
var marker = new google.maps.Marker(markerOptions);
And we have to set the map:
marker.setMap(map);
Now if you want to rotate the image you need to do the next:
Change the imagePath variable's value to 'img/customMarker.png#yourId'
Set rotation value with css (e.g. with JQuery)
Let's see
imagePath = 'img/customMarker.png#markerOne';
$('img[src="img/customMarker.png#markerOne"]').css({
'transform': 'rotate(45deg)'
});
Of course you can do it fancier:
function rotateMarker(selector, degree){
$('img[src="img/customMarker.png#'+selector+'"]').css({
'transform': 'rotate('+degree+'deg)'
});
}
And your call:
rotateMarker('markerOne', 45);
That's all.
I hope it could be helpful.
I have done the rotation in v3 with the following code:
<canvas id="carcanvas" width="1" height="1"></canvas>
if (document.getElementById('carcanvas').getContext) {
var supportsCanvas = true;
} else {
var supportsCanvas = false;
}
var rImg = new Image();
rImg.src='/images/cariconl.png';
// Returns the bearing in radians between two points.
function bearing( from, to ) {
// Convert to radians.
var lat1 = from.latRadians();
var lon1 = from.lngRadians();
var lat2 = to.latRadians();
var lon2 = to.lngRadians();
// Compute the angle.
var angle = - Math.atan2( Math.sin( lon1 - lon2 ) * Math.cos( lat2 ), Math.cos( lat1 ) * Math.sin( lat2 ) - Math.sin( lat1 ) * Math.cos( lat2 ) * Math.cos( lon1 - lon2 ) );
if ( angle < 0.0 )
angle += Math.PI * 2.0;
if (angle == 0) {angle=1.5;}
return angle;
}
function plotcar() {
canvas = document.getElementById("carcanvas").getContext('2d');
var cosa = Math.cos(angle);
var sina = Math.sin(angle);
canvas.clearRect(0,0,32,32);
canvas.save();
canvas.rotate(angle);
canvas.translate(16*sina+16*cosa,16*cosa-16*sina);
canvas.drawImage(rImg,-16,-16);
canvas.restore();
}
and in the animation method :
if (supportsCanvas) {
angle = bearing(new google.maps.LatLng(lat1, lng1),new google.maps.LatLng(lat2, lng2));
plotcar();
}
I hope that help.
You did not state it in your question, but I am assuming that you want this rotation in relation to a line between point a and point b, which would be their path. In order to make a google svg icon that can be rotated, you will want to use the google symbol class object to define the properties of your marker symbol. This does not use a full .svg file, but only the d attribute of the path. Note that the google symbol class can only take one path per marker.
Additional attributes for color, stroke, width, opacity, etc. may be set after the marker has been created with javascript (updating the marker object properties directly), or with CSS (updating the marker properties by adding and removing classes).
As an example, the following will create an arrow marker that can be dragged, and it will be rotated around the point on the map that is the lat and long for the marker even after it is moved.
The HTML
<body id="document_body" onload="init();">
<div id="rotation_control">
Heading°<input id="rotation_value" type="number" size="3" value="0" onchange="setRotation();" />
</div>
<div id="map_canvas"></div>
</body>
The CSS (yes,verbose... I hate ugly)
#document_body {
margin:0;
border: 0;
padding: 10px;
font-family: Arial,sans-serif;
font-size: 14px;
font-weight: bold;
color: #f0f9f9;
text-align: center;
text-shadow: 1px 1px 1px #000;
background:#1f1f1f;
}
#map_canvas, #rotation_control {
margin: 1px;
border:1px solid #000;
background:#444;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
#map_canvas {
width: 100%;
height: 360px;
}
#rotation_control {
width: auto;
padding:5px;
}
#rotation_value {
margin: 1px;
border:1px solid #999;
width: 60px;
padding:2px;
font-weight: bold;
color: #00cc00;
text-align: center;
background:#111;
border-radius: 4px;
}
The Javascript (in plain vanilla flavor for understanding core concepts)
var map, arrow_marker, arrow_options;
var map_center = {lat:41.0, lng:-103.0};
var arrow_icon = {
path: 'M -1.1500216e-4,0 C 0.281648,0 0.547084,-0.13447 0.718801,-0.36481 l 17.093151,-22.89064 c 0.125766,-0.16746 0.188044,-0.36854 0.188044,-0.56899 0,-0.19797 -0.06107,-0.39532 -0.182601,-0.56215 -0.245484,-0.33555 -0.678404,-0.46068 -1.057513,-0.30629 l -11.318243,4.60303 0,-26.97635 C 5.441639,-47.58228 5.035926,-48 4.534681,-48 l -9.06959,0 c -0.501246,0 -0.906959,0.41772 -0.906959,0.9338 l 0,26.97635 -11.317637,-4.60303 c -0.379109,-0.15439 -0.812031,-0.0286 -1.057515,0.30629 -0.245483,0.33492 -0.244275,0.79809 0.0055,1.13114 L -0.718973,-0.36481 C -0.547255,-0.13509 -0.281818,0 -5.7002158e-5,0 Z',
strokeColor: 'black',
strokeOpacity: 1,
strokeWeight: 1,
fillColor: '#fefe99',
fillOpacity: 1,
rotation: 0,
scale: 1.0
};
function init(){
map = new google.maps.Map(document.getElementById('map_canvas'), {
center: map_center,
zoom: 4,
mapTypeId: google.maps.MapTypeId.HYBRID
});
arrow_options = {
position: map_center,
icon: arrow_icon,
clickable: false,
draggable: true,
crossOnDrag: true,
visible: true,
animation: 0,
title: 'I am a Draggable-Rotatable Marker!'
};
arrow_marker = new google.maps.Marker(arrow_options);
arrow_marker.setMap(map);
}
function setRotation(){
var heading = parseInt(document.getElementById('rotation_value').value);
if (isNaN(heading)) heading = 0;
if (heading < 0) heading = 359;
if (heading > 359) heading = 0;
arrow_icon.rotation = heading;
arrow_marker.setOptions({icon:arrow_icon});
document.getElementById('rotation_value').value = heading;
}
And the best yet, doing it this way assures the marker is a Google MVC object, giving it all the additional methods provided by the MVC object.
If you must have multi-colored images as your marker, then creating a .png sprite sheet with a rendition of the image at all the angles you want it to be shown, and then problematically select the correct image to use based on the computed bearing between the two points you are using. However,this would not be an SVG image, but a regular marker image.
Hope this helps in making some decisions regarding your map markers.
Nobody mentioned about using pre-rotated icons. Depending on your application, you could take one icon and rotate it +10 degrees, +20 degrees ... +350 degrees and instead of rotating marker itself, just assign different icon to it - one out of 36 if 10 degrees resolution is good enough. That's also very light on client's resources.
In the example below I generated 36 icons, every one of them is 10 degrees rotated. Their names are: icon0.png, icon10.png, icon20.png, ... icon340.png, icon350.png, icon360.png. The 0 and 360 are the very same icon (e.g symlink)
var rotation = 123 // degrees
var iconName = "icon" + (Math.round(rotation/10)*10).toString() + ".png"
var marker = new google.maps.Marker({
icon: iconName
})
I was able to solve this pretty easily but using the marker.icon.rotation option pointing to a custom symbol that uses the svg path syntax.
$scope.triangle = {
path: 'M 0 0 L -35 -100 L 35 -100 z',
fillColor: '#3884ff',
fillOpacity: 0.7,
scale: 1,
strokeColor: '#356cde',
rotation: 90,
strokeWeight: 1
};
If using angular-google-maps it is trivial to bind a ui control to change the triangle.rotation.
Like I did with this slider.
<slider ng-model="triangle.rotation" floor="0" ceiling="359" step="5" precsion="1"></slider>
But you could use a forum too.
here is my plunker http://plnkr.co/edit/x0egXI
This is how i implemented my image rotated, I considered the marker in the form of overlay and that overlay is position to the position, Below code will be added .
Without using any additional library it is rotated,And you need to workaround to add click events and mouse events for the overlay, not similar to marker click events.
With googleMap markers customization, there will be addition memory usage in the map.
This will also reduce the memory consumption of custom markers in your map.
<html>
<head>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<style>html, body {
height: 100%;
margin: 0;
padding: 0;
}
#map_canvas {
height: 100%;
}
div.htmlMarker {
color: red;
cursor: pointer;
}
</style>
</head>
<body onload="initialize()">
<div id="map_canvas"></div>
</body>
<script>
var overlay;
function initialize() {
var myLatLng = new google.maps.LatLng(40, -100);
var mapOptions = {
zoom: 10,
center: myLatLng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var gmap = new google.maps.Map(document.getElementById('map_canvas'), mapOptions);
function HTMLMarker(lat, lng, rotation) {
this.lat = lat;
this.lng = lng;
this.rotation = rotation;
this.pos = new google.maps.LatLng(lat, lng);
}
HTMLMarker.prototype = new google.maps.OverlayView();
HTMLMarker.prototype.onRemove = function () {}
//Initilize your html element here
HTMLMarker.prototype.onAdd = function () {
div = document.createElement('DIV');
div.style.position='absolute';
div.style.transform='rotate('+this.rotation +'deg)';
div.style.MozTransform='rotate('+this.rotation +'deg)';
div.className = "htmlMarker";
//image source use your own image in src
div.innerHTML = '<img src="prudvi.png" alt="Mountain View" style="width:25px;height:22px">' ;
var panes = this.getPanes();
panes.overlayImage.appendChild(div);
this.div=div;
}
HTMLMarker.prototype.draw = function () {
var overlayProjection = this.getProjection();
var position = overlayProjection.fromLatLngToDivPixel(this.pos);
var panes = this.getPanes();
this.div.style.left = position.x + 'px';
this.div.style.top = position.y - 30 + 'px';
}
//Added 50 marker with random latlng location and random rotation,
for (i = 0; i < 50; i++) {
var PoslatLng = new google.maps.LatLng(myLatLng.lat() + Math.random() - 0.5, myLatLng.lng() + Math.random() - 0.5);
var htmlMarker = new HTMLMarker(myLatLng.lat() + Math.random() - 0.5,myLatLng.lng() + Math.random() - 0.5, Math.floor(Math.random() * 359));
htmlMarker.setMap(gmap);
google.maps.event.addListener(htmlMarker, 'click', function() {
console.log('clciked')
gmap.setZoom(8);
gmap.setCenter(htmlMarker.getPosition());
});
}
}
</script>
</html>
You could call the yourmarker.setIcon(canvas.toDataUrlOrSomeThig) every time the image changes. I don't see anything in the api reference for using the canvas element directly, except if you implement you own google.maps.OverlayView.
If you only want animation you could use a gif, and add the marker option optimized: false to it.
The easiest way may be to use the rotation property of google.maps.Symbol. Just set it as a property of your icon when creating or updating your marker:
new google.maps.Marker({
position: map.getCenter(),
icon: {
path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW,
scale: 7,
rotation: 193
},
map: map
});
Plunker
The idea is to first draw the rotated marker image on a hidden canvas.
Say, you have a hidden canvas:
<canvas id="carCanvas" width="50" height="50" style="display:none"></canvas>
Now you can do this:
function updateCarMarker(i,lat, lng, icon = "img/carIcon.png") {
var latLong = new google.maps.LatLng(lat, lng);
if (!carMarkers[i]){
var carImage = new Image();
carImage.onload = ()=>{
drawMovedCar(i,latLong,carImage);
}
carImage.src=icon;
} else {
drawMovedCar(i,latLong,carMarkers[i].carImage);
}
}
function drawMovedCar(i,latLong,I){
let m=carMarkers[i];
let canvas = document.getElementById("carCanvas");
let C = canvas.getContext('2d');
if (m){
var distance = google.maps.geometry.spherical.computeDistanceBetween(
m.getPosition(), latLong);
var deg = (distance<2)?carMarkers[i].deg
:google.maps.geometry.spherical.computeHeading(m, latLong);
carMarkers[i].setMap(null);
} else {
var deg=0;
}
C.save();
C.clearRect(0, 0, canvas.width, canvas.height);
C.translate(canvas.width/2,canvas.height/2);
C.rotate(deg * Math.PI / 180);
C.scale(0.4,0.4);
C.drawImage(I,-I.width/2,-I.height/2,I.width,I.height);
C.restore();
if (!m){
m = new google.maps.Marker({
position: latLong,
map: map,
icon: canvas.toDataURL("image/png",1)
});
m.deg = deg;
m.carImage = I;
carMarkers[i]=m;
} else {
m.setIcon(canvas.toDataURL("image/png",1));
m.setPosition(latLong);
}
}
The above is my original code. I have left it intact so that you can see my other optimizations.
Using MarkerWithLabel Library, you can achieve that in such way:
var ico = document.createElement('img');
ico.src = 'ImageSource';
ico.setAttribute('style', 'transform:rotate('30deg);');
mapMarkers[0].labelContent = ico;
mapMarkers[0].label.draw();
Assuming you only use that image within Google Maps, you can do the following
bearing = 20
document.querySelectorAll('img[src="/images/imageName"]').forEach((node) => {
node.style['transform'] = `rotate(${bearing}deg)`
node.style['webkitTransform'] = `rotate(${bearing}deg)`
node.style['MozTransform'] = `rotate(${bearing}deg)`
node.style['msTransform'] = `rotate(${bearing}deg)`
node.style['OTransform'] = `rotate(${bearing}deg)`
})
This reaches down the dom tree and sets the transform for the marker icon to rotate the degrees you want. The image imageName should be facing North
Not to sure if the webkit, Moz, ms and O version are needed but hey 🤷🏽♂️ cant hurt
If you are using SVG, Then this is the best way to rotate it.
let marker_, svg_, size_ = 100, rotation_ = 50
// Get SVG
fetch('https://upload.wikimedia.org/wikipedia/commons/7/78/Space-shuttle.svg')
.then(response => response.text())
.then(text => {
svg_ = text;
svg_ = svg_
.replace(/^<\?(.+)\?>$/gm, '') // unsupported unnecessary line
// You can replace anything you want, but first of all check your svg code
.replace(/width.+\Wheight\S+/,
'width="{{width}}" height="{{height}}" transform="{{transform}}" ')
// Load Map
initMap()
})
function getIcon(rotation){
return {url:`data:image/svg+xml;charset=utf-8,
${encodeURIComponent(svg_
.replace('{{width}}', 100)
.replace('{{height}}', 100)
.replace('{{transform}}', `rotate(${rotation},0,0)`))}`,anchor: new google.maps.Point(50, 50),
origin: new google.maps.Point(0, 0)}
}
// Map
function initMap() {
const position = {lat: 36.720426, lng: -4.412573};
const map = new google.maps.Map(document.getElementById("map"), {
zoom: 19,
center: position
})
marker_ = new google.maps.Marker({
position: position,
map: map,
icon: getIcon(rotation_)
})
}
// Change rotation
$input_ = document.querySelector('input')
$input_.value = rotation_
$input_.onchange = () => {
marker_.setIcon(getIcon(parseInt($input_.value))
)
}
* {
padding: 0;
margin: 0;
}
#map {
width: 100%;
height: 100vh;
}
input {
position: fixed;
z-index: 1;
margin: 100px;
padding: 10px;
border-radius: 2px;
background-color: red;
border: none;
color: white;
font-family: 'Roboto';
width: 70px;
}
<script src="https://maps.google.com/maps/api/js"></script>
<input type="number" placeholder="rotation">
<div id="map"></div>
I have found an easy way to rotate the png image marker for the google marker. Create an custom marker overriding google.maps.OverlayView and rotate the image simply with css/inline style
export const createCustomMarker = ({ OverlayView = google.maps.OverlayView, ...args }) => {
class GoogleMarker extends OverlayView {
options: any = {};
div: any = null;
innerHtml: any = null;
constructor(options) {
super();
this.options = options;
this.setMap(options.map);
}
createDiv() {
const options = this.options;
this.div = document.createElement('div');
this.div.style.position = 'absolute';
this.setRotation(this.options.rotation);
if (options.icon) {
this.setInnerHtml(this.getInnerImageHtml(options));
}
}
getInnerImageHtml(options) {
const size = this.getSize(options);
const label = this.options.label;
const labelHtml = label ? `<span style="color:black;margin-left: -40px;width: 100px;text-align: center;display: block;font-weight:bold;">${label}</span>` : "";
return `<img style="height:${size.height}px;width:${size.width}px" id="${options.id || ''}" src="${options.icon}">${labelHtml}`;
}
addListeners() {
const self = this;
google.maps.event.addDomListener(this.div, 'click', event => {
google.maps.event.trigger(self, 'click');
});
this.div.onmouseenter = function () {
debugger
google.maps.event.trigger(self, 'onmouseenter');
}
this.div.onmouseover = function () {
google.maps.event.trigger(self, 'onmouseover');
}
this.div.onmouseleave = function () {
google.maps.event.trigger(self, 'onmouseleave');
}
this.div.onmouseout = function () {
google.maps.event.trigger(self, 'onmouseout');
}
}
appendDivToOverlay(appendDiv: any) {
const panes: google.maps.MapPanes = this.getPanes();
panes.floatPane.appendChild(appendDiv);
}
setRotation(degrees: number) {
if (this.div) {
this.div.style.transform = 'rotate(' + degrees + 'deg)';
}
this.options.rotation = degrees;
}
getRotation() {
return this.options.rotation;
}
setInnerHtml(html: string) {
this.innerHtml = html;
this.div.innerHTML = this.innerHtml;
}
private positionDiv(div: any, options: any) {
if (div != null) {
const point = this.getProjection().fromLatLngToDivPixel(options.latlng);
if (point) {
const size = this.getSize(options);
const anchor = options.anchor ? options.anchor : new google.maps.Point((size.width / 2), (size.height / 2))
const leftAnchor = anchor.x;
const topAnchor = anchor.y;
div.style.left = `${point.x - leftAnchor}px`;
div.style.top = `${point.y - topAnchor}px`;
}
}
}
private getSize(options) {
const size = options.size || { height: 52, width: 52 };
return size;
}
draw() {
if (!this.div) {
this.createDiv();
this.appendDivToOverlay(this.div);
this.addListeners();
}
this.positionDiv(this.div, this.options);
}
remove() {
if (this.div) {
this.div.parentNode.removeChild(this.div);
this.div = null;
}
}
setVisible(value: boolean) {
if (this.div) {
this.div.style["display"] = value ? "block" : "none";
}
}
getVisible() {
if (this.div) {
return this.div.style["display"] == "none";
}
return false;
}
setPosition(position) {
this.options.latlng = position;
this.infoOptions.latlng = position;
this.positionDiv(this.div, this.options);
}
getPosition() {
return this.options.latlng;
}
getDraggable() {
return false;
}
isHTML(html: string) {
return /<([A-Za-z][A-Za-z0-9]*)\b[^>]*>(.*?)<\/\1>/.test(html);
}
}
return new GoogleMarker(args)
}
After creating this custom marker - Initialize the marker in the following way
import { createCarMarker } from "./marker.component"; // dynamic path to component
let marker = createCarMarker({
id: id, // will add id to the parent container div
latlng: new google.maps.LatLng(0, 0), // replace latitude-longitude with your values
map: this.map,
size: new google.maps.Size(52, 52), // replace the image size with your values
rotation: markerData.direction, // Provide values in degrees
icon: iconUrl, // Replace it with your image url
label: markerLabel // Provide marker label. Optional field
});
Now simply rotate the marker using the following method
marker.setRotation(180); // You just need to call only this method every-time the degrees changes.
To listen the changes on the marker.
google.maps.event.addDomListener(marker, 'click', function (event) {
});
google.maps.event.addListener(marker, 'onmouseenter', function (event) {
});
google.maps.event.addListener(marker, 'onmouseleave', function (event) {
});
google.maps.event.addListener(marker, 'onmouseover', function (event) {
});
google.maps.event.addListener(marker, 'onmouseout', function (event) {
});
You can customize the listeners or add new/update in the custom marker class according to your requirement.
var icon = {
path: aeroplanePath/image,
fillColor: '#0000FF',
fillOpacity: .6,
anchor: new google.maps.Point(0,0),
strokeWeight: 0,
scale: 1,
rotation: 180
}
var marker = new google.maps.Marker({
position: positions[k],
icon: icon,
draggable: true,
title: "BOING-707",
});