I am trying to build a simple Angular 1.5 component containing a Leaflet map.
Here is my component's JS:
// Extend the leaflet js to support the topo module
L.TopoJSON = L.GeoJSON.extend({
addData: function(jsonData) {
if (jsonData.type === "Topology") {
for (var key in jsonData.objects) {
geojson = topojson.feature(jsonData, jsonData.objects[key]);
L.GeoJSON.prototype.addData.call(this, geojson);
}
} else {
L.GeoJSON.prototype.addData.call(this, jsonData);
}
}
});
angular
.module('synthApp')
.component('map', {
templateUrl: 'app/components/map.template.html',
controller: MapController,
bindings: {
name: '#'
}
});
MapController.$inject = [
'$window',
'$rootScope'
];
function MapController(
$window,
$rootScope
) {
var $ctrl = this;
$ctrl.map = null;
$ctrl.markers = [];
// Set layer style
$ctrl.handleLayer = function(layer) {
layer.setStyle({
fillColor: '#eee',
weight: 1,
opacity: 1,
color: '#aaa',
fillOpacity: 1,
clickable: false
});
};
$ctrl.$onInit = function() {
$ctrl.map = $window.L.map($ctrl.name, {
maxZoom: 10,
minZoom: 1,
scrollWheelZoom: false,
attributionControl: false,
tap: false,
touchZoom: true,
zoomControl: true,
crs: $window.L.CRS.EPSG4326
});
var topoLayer = new $window.L.TopoJSON();
var td;
$.getJSON("../map.json", function(json) {
td = json;
console.log(td);
});
topoLayer.addData(td);
topoLayer.addTo($ctrl.map);
topoLayer.eachLayer($ctrl.handleLayer);
$ctrl.map.setView([0,0], 2);
};
}
Here is the content of app/components/map.template.html:
<div id="$ctrl.name" style="background:#c2dfff;height:500px;width:100%">
I am trying to use the component like so:
<map name="rt-world-2"></map>
However, I am seeing an error Error: Map container not found. occurring on the line where I call $ctrl.map = $window.L.map($ctrl.name, {...}) in the map component's init.
Are there some kind of limitations with using Angular in conjunction with Leaflet? Any ideas what the error message means?
In the template:
<div id="$ctrl.name" style="background:#c2dfff;height:500px;width:100%">
replace the binding expression for id attribute with id="{{$ctrl.name}}"
And then wrap the map initialization with $timeout to ensure the template is rendered:
$ctrl.$onInit = function () {
$timeout(function () {
//map initialization goes here..
});
};
Example
angular
.module('synthApp', [])
.component('map', {
//templateUrl: 'map.template.html',
template: '<div id="{{$ctrl.name}}" style="background:#c2dfff;height:500px;width:100%"/>',
controller: MapController,
bindings: {
name: '#'
}
});
MapController.$inject = [
'$window',
'$rootScope',
'$timeout'
];
function MapController(
$window,
$rootScope,
$timeout
) {
var $ctrl = this;
$ctrl.map = null;
$ctrl.markers = [];
$ctrl.$onInit = function () {
$timeout(function () {
$ctrl.map = $window.L.map($ctrl.name, {
maxZoom: 10,
minZoom: 1,
scrollWheelZoom: false,
attributionControl: false,
tap: false,
touchZoom: true,
zoomControl: true,
crs: $window.L.CRS.EPSG4326
});
$ctrl.map.setView([0, 0], 2);
$window.L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{ maxZoom: 18 }).addTo($ctrl.map);
});
};
}
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" />
<script src="//cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.js"></script>
<div ng-app="synthApp">
<map name="rt-world-2"></map>
</div>
Related
I am making a vue project and I want to use leaflet inside of my components. I have the map showing but I run into an error when I try to add a marker to the map. I get
Uncaught TypeError: events.forEach is not a function
at VueComponent.addEvents (VM2537 Map.vue:35)
at e.boundFn (VM2533 vue.esm.js:191)
at HTMLAnchorElement. (leaflet.contextmenu.js:328)
at HTMLAnchorElement.r (leaflet.js:5)
<template>
<div>
<div id="map" class="map" style="height: 781px;"></div>
</div>
</template>
<script>
export default {
data() {
return {
map: [],
markers: null
};
},
computed: {
events() {
return this.$store.state.events;
}
},
watch: {
events(val) {
this.removeEvents();
this.addEvents(val);
}
},
methods: {
addEvents(events) {
const map = this.map;
const markers = L.markerClusterGroup();
const store = this.$store;
events.forEach(event => {
let marker = L.marker(e.latlng, { draggable: true })
.on("click", el => {
store.commit("locationsMap_center", e.latlng);
})
//.bindPopup(`<b> ${event.id} </b> ${event.name}`)
.addTo(this.map);
markers.addLayer(marker);
});
map.addLayer(markers);
this.markers = markers;
},
removeEvent() {
this.map.removeLayer(this.markers);
this.markers = null;
}
},
mounted() {
const map = L.map("map", {
contextmenu: true,
contextmenuWidth: 140,
contextmenuItems: [
{
text: "Add Event Here",
callback: this.addEvents
}
]
}).setView([0, 0], 1);
L.tileLayer("/static/map/{z}/{x}/{y}.png", {
maxZoom: 4,
minZoom: 3,
continuousWorld: false,
noWrap: true,
crs: L.CRS.Simple
}).addTo(map);
this.map = map;
}
};
</script>
New2Dis,
Here is your example running in a jsfiddle.
computed: {
events: function () {
return this.store.events;
}
},
watch: {
events: function (val) {
this.removeEvents();
this.addEvents(val);
}
},
methods: {
addEvents(events) {
console.log("hoi")
const map = this.map;
const markers = L.markerClusterGroup();
const store = this.$store;
events.forEach(event => {
let marker = L.marker(event.latlng, { draggable: true })
.on("click", el => {
//store.commit("locationsMap_center", event.latlng);
})
.bindPopup(`<b> ${event.id} </b> ${event.name}`)
.addTo(this.map);
markers.addLayer(marker);
});
map.addLayer(markers);
this.markers = markers;
},
removeEvents() {
if (this.markers != null) {
this.map.removeLayer(this.markers);
this.markers = null;
}
}
},
I did replace some things to make it works, like the $store as I don't have it, and removeEvent was not written correctly, so I'm not sure what I actually fixed...
I have also created a plugin to make it easy to use Leaflet with Vue.
You can find it here
You will also find a plugin for Cluster group here
Give it a try and let me know what you think.
I want to show Google map in my html . here is my code:
html:
<div class="map-content">
<div map-marker="" ng-model="searchLocation" class="mapmarker"></div>
</div>
js:
app.directive('mapMarker',function(){
return {
restrict: 'EA',
require: '?ngModel',
scope:{
searchLocation: '=ngModel'
},
controller: function ($scope) {
$scope.searchLocation = {
latitude: 48.137273,
longitude: 11.575251
};
},
resolve: {
load: function () {
}
},
link: function(scope , element, attrs , ngModel){
var mapOptions;
var googleMap;
var searchMarker;
var searchLatLng;
ngModel.$render = function(){
searchLatLng = new google.maps.LatLng(scope.searchLocation.latitude, scope.searchLocation.longitude);
mapOptions = {
center: searchLatLng,
zoom: 12,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
googleMap = new google.maps.Map(element[0],mapOptions);
searchMarker = new google.maps.Marker({
position: searchLatLng,
map: googleMap,
draggable: true
});
google.maps.event.addListener(searchMarker, 'dragend', function(){
scope.$apply(function(){
scope.searchLocation.latitude = searchMarker.getPosition().lat();
scope.searchLocation.longitude = searchMarker.getPosition().lng();
});
}.bind(this));
};
scope.$watch('searchMarker', function(value){
var myPosition = new google.maps.LatLng(scope.searchLocation.latitude, scope.searchLocation.longitude);
searchMarker.setPosition(myPosition);
}, true);
}
}
});
Also, I include
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
It knows script file but it can't show map on my html. Any suggestion?
With a little bit of modifications your example works: see my plunkr.
angular
.module('app', [])
.directive('mapMarker',function(){
return {
restrict: 'EA',
scope:{
searchLocation: '=mapMarker'
},
controller: function ($scope) {
$scope.searchLocation = {
latitude: 48.137273,
longitude: 11.575251
};
},
link: function(scope , element, attrs , ngModel){
var mapOptions;
var googleMap;
var searchMarker;
var searchLatLng;
searchLatLng = new google.maps.LatLng(scope.searchLocation.latitude, scope.searchLocation.longitude);
mapOptions = {
center: searchLatLng,
zoom: 12,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
googleMap = new google.maps.Map(element[0], mapOptions);
searchMarker = new google.maps.Marker({
position: searchLatLng,
map: googleMap,
draggable: true
});
google.maps.event.addListener(searchMarker, 'dragend', function(){
scope.$apply(function(){
scope.searchLocation.latitude = searchMarker.getPosition().lat();
scope.searchLocation.longitude = searchMarker.getPosition().lng();
});
});
scope.$watch('searchMarker', function(value){
var myPosition = new google.maps.LatLng(scope.searchLocation.latitude, scope.searchLocation.longitude);
searchMarker.setPosition(myPosition);
}, true);
}
}
});
You don't need ngModel. Especially ngModel.$render. It is needed for inputs, which is not your case.
Container for map has to have initial size: width and height.
I want to achieve something similar to this http://jsfiddle.net/sya8gn0w/1/.
Now the problem is I have my own custom directive to display map. I want a function in child controller which will achieve above mentioned functionality on some button click.
eg. The place marker functionality triggers only if I click on some button.
Present code -
function ParentCtrl($scope){
var mainCtrl = this;
angular.extend(mainCtrl , {
map: {
center: {
latitude: 18.5,
longitude: 73.85
},
zoom: 13,
markers: [],
events: {
click: function (map, eventName, originalEventArgs) {
var e = originalEventArgs[0];
var lat = e.latLng.lat(), lon = e.latLng.lng();
var marker = {
id: Date.now(),
coords: {
latitude: lat,
longitude: lon
}
};
mainCtrl .map.markers.pop();
mainCtrl .map.markers.push(marker);
console.log(mainCtrl .map.markers);
console.log("latitude : "+lat+" longitude : "+lon);
$scope.$apply();
}
}
}
});
};
I want to move the functionality present in 'click:' part of angular.extend to a function presents in ChildCtrl controller. Is it possible ?
Ortherwise suggest me different approach to achieve this.
I don't know whether my answer going to help you or not.
Recently I worked on GoogleMap, I want to share what I know.
angular.extend(scope, {
map: {
show: true,
control: {},
version: 'uknown',
center: {
latitude: 0,
longitude: 0
},
options: {
streetViewControl: false,
panControl: false,
maxZoom: 10,
minZoom: 1
},
zoom: 2,
dragging: false,
bounds: {},
markers: scope.markers,
doClusterMarkers: true,
currentClusterType: 'standard',
clusterTypes: clusterTypes,
selectedClusterTypes: selectedClusterTypes,
clusterOptions: selectedClusterTypes['standard'],
events: {
tilesloaded: function(map, eventName, originalEventArgs) {},
click: function(mapModel, eventName, originalEventArgs) {},
dragend: function() {}
},
toggleColor: function(color) {
if (color === 'red') {
return '#6060FB';
} else {
return 'red';
}
}
}
Here the statement markers: scope.markers will help to create new markers or to remove old markers.
In "ChildCtrl" you write a function to update scope.markers object of parent controller.
You could declare the function for adding a marker in a separate controller
and then utilize $controller service to inject that controller into another controller:
angular.module('main', ['uiGmapgoogle-maps'])
.controller('MapCtrl', function ($scope,$controller) {
$controller('MarkerCtrl', {$scope: $scope});
angular.extend($scope, {
map: {
center: {
latitude: 42.3349940452867,
longitude:-71.0353168884369
},
zoom: 11,
markers: [],
events: {
click: function (map, eventName, originalEventArgs) {
var e = originalEventArgs[0];
$scope.addMarker(e.latLng);
$scope.$apply();
}
}
}
});
})
.controller('MarkerCtrl', function($scope) {
angular.extend($scope, {
addMarker: function(latLng) {
var marker = {
id: Date.now(),
coords: {
latitude: latLng.lat(),
longitude: latLng.lng()
}
};
$scope.map.markers.push(marker);
}
});
});
Plunker
I used different but better approach to achieve the desired results.
Found this solution on fiddle . Hope this would be helpful for someone else also.
function dragMarkerService(resolveAddressService){
this.placeMarker = function(location, idValue, map){
var myMarker = new google.maps.Marker({
position: new google.maps.LatLng(location.latitude, location.longitude),
draggable: true
});
google.maps.event.addListener(myMarker, 'dragend', function (evt) {
location.latitude = evt.latLng.lat();
location.longitude = evt.latLng.lng();
resolveAddressService.resolveAddress(location, idValue)
});
google.maps.event.addListener(myMarker, 'dragstart', function (evt) {
angular.element(idValue).val() = 'Currently dragging marker...';
});
map.setCenter(myMarker.position);
myMarker.setMap(map);
}
};
I am trying to display a pop up window in an angular leaflet map using prune cluster. However, I am getting an error that leafletView is an unknown provider. I have followed the examples on this page but i have been unsuccessful - https://github.com/SINTEF-9012/PruneCluster. Here is my code:
angular.module('bizvizmap').controller('controller', [
'$scope', '$http', '$filter', 'leafletData', 'leafletView',
function ($scope, $http, $filter, leafletData, leafletView){
$scope.center = {
lat: 39.5500507,
lng: -105.7820674,
zoom: 4
},
$scope.defaults = {
scrollWheelZoom: true
},
$scope.events = {
map: {
enable: ['zoomstart', 'drag', 'click', 'mousemove'],
logic: 'emit'
}
},
$scope.layers = {
baselayers: {
osm: {
name: 'OpenStreetMap',
url: 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
type: 'xyz'
}
},
markers:{}
},
$scope.map = null;
leafletData.getMap("bizvizmap").then(function(map){
$scope.map = map;
});
function renderMarkers(data, map){
var markerLayer = new PruneClusterForLeaflet();
for (var i=0; i < data.length; i++){
var marker = new PruneCluster.Marker(data[i].geometry.coordinates[1], data[i].geometry.coordinates[0]);
popup: "Marker";
markerLayer.RegisterMarker(marker);
}
map.addLayer(markerLayer);
markerLayer.ProcessView();
}
//Display PopUp
leafletView.PrepareLeafletMarker = function (marker, data) {
if (marker.getPopup()) {
marker.setPopupContent(data.company);
} else {
marker.bindPopup(data.company);
}
};
$scope.geojson = {};
$http.get('data/bizvizmap.geojson').success(function (data){
$scope.data = data;
// Render clustered markers
renderMarkers(data.features, $scope.map);
});
// Filtering markers
$scope.search = {
'NAICS':'',
'SIC':''
};
$scope.$watch('search', function(newVal, oldVal){
if(!angular.equals(newVal, oldVal)) {
var geojson = angular.copy($scope.data);
angular.forEach(newVal, function(value, property){
console.log(property + ':' + value);
if (value !== ''){
geojson = $filter('filter')(geojson, property, value);
}
});
function removeMarkers(data, map){
map.removeLayer(markerLayer);
markerLayer.ProcessView();
}
//map.removeLayer(markerLayer);
// Remove all the markers
$scope.geojson.data = geojson;
//renderMarkers(data.features, $scope.map);
} else {
$scope.geojson.data = $scope.data;
}
}, true);
}
]);
I think that the object that have to be modified in order to add popups is the actual prunecluster were you are registering the markers.
var markerLayer = new PruneClusterForLeaflet();
markerLayer.PrepareLeafletMarker = function (marker, data) {
if (marker.getPopup()) {
marker.setPopupContent(data.company);
} else {
marker.bindPopup(data.company);
}
};
I have such Andularjs code in my app:
angular.module('FlickrMaps', ['SignalR'])
.factory('PhotoMarkers', ['$rootScope', 'Hub', function ($rootScope, Hub) {
var PhotoMarkers = this;
//Hub setup
var hub = new Hub('photos', {
listeners: {
'addTotalPhotosCount': function (total) {
PhotoMarkers.totalCount = total;
$rootScope.$apply();
},
'initPhotoMarker': function (photo) {
var photolocation = new window.google.maps.LatLng(photo.Latitude, photo.Longitude);
var marker = new window.google.maps.Marker({
position: photolocation,
title: photo.Title,
photoThumb: photo.PhotoThumbScr,
photoOriginal: photo.PhotoOriginalScr
});
window.google.maps.event.addListener(marker, 'click', function () {
$rootScope.$broadcast('markerClicked', marker);
});
PhotoMarkers.all.push(marker);
$rootScope.$apply();
},
'photosProcessed': function () {
PhotoMarkers.processedPhotosCount++;
if (PhotoMarkers.processedPhotosCount == PhotoMarkers.totalCount && PhotoMarkers.processedPhotosCount > 0) {
$rootScope.$broadcast('markersLoaded');
}
$rootScope.$apply();
},
},
methods: ['loadRecentPhotos'],
errorHandler: function (error) {
console.error(error);
}
});
//Variables
PhotoMarkers.all = [];
PhotoMarkers.processedPhotosCount = 0;
PhotoMarkers.totalCount = 0;
//Methods
PhotoMarkers.load = function () {
hub.promise.done(function () { //very important!
hub.loadRecentPhotos();
});
};
return PhotoMarkers;
}])
.controller('MapController', ['$scope', 'PhotoMarkers', function ($scope, PhotoMarkers) {
$scope.markers = PhotoMarkers;
$scope.image = '123';
$('#myModal').modal({
backdrop: 'static',
keyboard: false
});
$scope.$on('markerClicked', function (event, data) {
console.log(data.photoThumb);
$scope.omfg = 'nkfglfghlfghflj';
});
$scope.$on('markersLoaded', function () {
$('#myModal').modal('toggle');
$scope.image = 'lol';
var mapOptions = {
zoom: 4,
center: new window.google.maps.LatLng(40.0000, -98.0000),
mapTypeId: window.google.maps.MapTypeId.TERRAIN
};
console.log(PhotoMarkers.all);
$scope.map = new window.google.maps.Map(document.getElementById('map-canvas'), mapOptions);
var markerCluster = new MarkerClusterer($scope.map, $scope.markers.all);
});
}])
$broadcast and $on services work finem but when I try to change $scope.image variable on markerClicked event it doesn't change on view page, but for markersLoaded it changes. Does anyone have any ideas? I tried lot of ways to fix it...
You need to wrap it inside $apply since you are in a callback outside angular:
window.google.maps.event.addListener(marker, 'click', function () {
$rootScope.$apply(function() {
$rootScope.$broadcast('markerClicked', marker);
});
});