Bing Maps infobox is undefined inside Angular component - javascript

I am creating a component in angular/typescript for a bing maps implementation. I went through the process to add an infobox to the map that would initially be not visible to the user. When the user clicks on any of the pushpins on the map the infobox is supposed to display.
However it does not and the property is shown as undefined.
Note: 'DataPoints' contains a list of objects that contains lat long coordinates and an arbitrary ID number.
import { Component, AfterViewInit } from '#angular/core';
import { DataPoint } from '../common/data-point'
import { DataPoints } from '../common/data-points'
#Component({
selector: 'app-simple-bing-map',
templateUrl: './simple-bing-map.component.html',
styleUrls: ['./simple-bing-map.component.css'],
providers: []
})
export class SimpleBingMapComponent implements AfterViewInit {
private map: any;
private infobox: any;
ngAfterViewInit() {
this.getMap();
}
populateMap(){
for(var i in DataPoints){
var pushpin = new Microsoft.Maps.Pushpin(new Microsoft.Maps.Location(DataPoints[i].Lat, DataPoints[i].Long) , null);
pushpin.metadata = {
title: "Test Pushpin",
description: DataPoints[i].ID,
};
//Add a click event handler to the pushpin.
Microsoft.Maps.Events.addHandler(pushpin, 'click', this.displayInfobox);
//place pushpin
this.map.entities.push(pushpin);
}
}
getMap() {
//check if Microsoft is available
if ((window as any).Microsoft && (window as any).Microsoft.Maps) {
//if it is available create map instance
this.map = new Microsoft.Maps.Map(document.getElementById('mapId'), {
credentials: 'Your Bing Maps Key Here',
});
//initialize infobox
this.infobox = new Microsoft.Maps.Infobox(this.map.getCenter(), {
title: 'Pushpins',
description: 'ID Number'
}
);
//hide infobox
this.infobox.setOptions({ visible: false })
//Assign the infobox to a map instance.
this.infobox.setMap(this.map);
this.populateMap();
}
//wait and try again
else {
setTimeout(() => { this.getMap() }, 1000);
}
}
displayInfobox(e) {
//hide any previous infobox
this.infobox.setOptions({ visible: false });
//Make sure the infobox has metadata to display.
if (e.target.metadata) {
//Set the infobox options with the metadata of the pushpin.
this.infobox.setOptions({
location: e.target.getLocation(),
title: e.target.metadata.title,
description: e.target.metadata.description,
visible: true
});
}
}
}
As stated earlier the map loads completely and works as it should. It is just after I enter the 'displayInfobox' method that things act weirdly.

To retain this inside displayInfobox method i would advice you using either bind method like:
Microsoft.Maps.Events.addHandler(pushpin, 'click', this.displayInfobox.bind(this));
or arrow function:
Microsoft.Maps.Events.addHandler(pushpin, 'click', (e) => this.displayInfobox(e));
For other solutions see
https://github.com/Microsoft/TypeScript/wiki/%27this%27-in-TypeScript

Related

Why can't I load 3D models with Mapbox GL JS and Threebox with this Angular code

Can anyone help me understand how I need to structure my code for this mapbox w/ threebox project in Angular?
I can't figure out why this code:
this.tb.loadObj(options, function (model) {
var house = model.setCoords(origin);
this.tb.add(house);
});
is throwing the following error:
ERROR Error: Uncaught (in promise): ReferenceError: tb is not defined
ReferenceError: tb is not defined
Even after seeming to recognize tb as defined when I run console.log statements within this code block. But then this error shows up right afterwards and my 3D model never loads.
Here is the full code for the component, any advice on how to solve this issue would be appreciated:
import { Component, OnInit } from '#angular/core';
import { environment } from '../../../environments/environment';
import {Threebox} from 'threebox-plugin';
import * as M from 'mapbox-gl';
#Component({
selector: 'app-map',
templateUrl: './map.component.html',
styleUrls: ['./map.component.css']
})
export class MapComponent implements OnInit {
/// default settings
long = -122.4192;
lat = 37.7793;
map: M.Map;
style = 'mapbox://styles/mapbox/light-v10';
// data
source: any;
markers: any;
// render
tb: Threebox;
constructor() { }
ngOnInit(): void {
(M as any).accessToken = environment.mapbox.accessToken;
this.buildMap();
this.map.on('style.load', this.onLoad.bind(this));
this.map.on('load', (event) => {
/// register source
this.map.addSource('localdata', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [this.long, this.lat]
},
properties: {
title: 'Mapbox',
description: 'San Francisco, California'
}
}]
}
});
/// get source
this.source = this.map.getSource('localdata')
// markers
this.source._data.features.forEach((marker) => {
var lng = marker['geometry']['coordinates'][0]
var lat = marker['geometry']['coordinates'][1]
// create a HTML element for each feature
var el = document.createElement('div');
el.className = 'marker';
// make a marker for each feature and add to the map
new M.Marker({color: 'black'})
.setLngLat([lng, lat])
.addTo(this.map);
});
});
// Add map controls
this.map.addControl(new M.NavigationControl());
this.map.on('mousemove', function (e) {
document.getElementById('info').innerHTML =
// e.point is the x, y coordinates of the mousemove event relative
// to the top-left corner of the map
// e.lngLat is the longitude, latitude geographical position of the event
e.lngLat.lat.toFixed(6) + ', ' + e.lngLat.lng.toFixed(6) ;
});
};
// functions
buildMap() {
this.map = new M.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v10',
zoom: 18,
center: [this.long, this.lat],
pitch: 60,
});
}
onLoad(){
this.map.addLayer({
id: 'house',
type: 'custom',
// renderingMode: '3d',
onAdd(map, mbxContext) {
this.tb = new Threebox(
map,
mbxContext,
{ defaultLights: true }
);
//starting location for both map and eventual sphere
var origin = [this.long, this.lat];
var options = {
// obj: 'src/assets/3d/house.gltf',
obj: 'https://docs.mapbox.com/mapbox-gl-js/assets/34M_17/34M_17.gltf',
type: 'gltf',
scale: 1,
units: 'meters',
rotation: { x: 90, y: 0, z: 0 } //default rotation
}
this.tb.loadObj(options, function (model) {
var house = model.setCoords(origin);
this.tb.add(house);
});
},
render: function (gl, matrix) {
this.tb.update();
}
});
};
}
tb needs to be global:
https://github.com/jscastro76/threebox/blob/master/docs/Threebox.md#threebox-instance
try:
(window as any).tb = new Threebox(map, mbxContext, {defaultLights: true});
and
window['tb'].loadObj(options, function (model) {
var house = model.setCoords(origin);
window['tb'].add(house);
});
It seems as window.tb solves that issue.
Initialise tb outside onAdd function.
this.tb = new Threebox(map, mbxContext, options);
window.tb = this.tb;
Now you can use either this.tb or window.tb because both are the same.

Using google map in Vue / Laravel

Trying to implement google map into Vue component. But having a hard time. Actually, there is no error. But no map also :) Okay, what I tried so far down below.
In laravel blade I set my api.
<script async defer src="https://maps.googleapis.com/maps/api/js?key={{env('GOOGLE_MAPS_API')}}&callback=initMap"></script>
Then in Vue component;
data() {
return {
mapName: "map",
//some other codes
}
},
mounted() {
this.fetchEstates();
},
methods: {
fetchEstates(page = 1) {
axios.get('/ajax', {
params: {
page
}}).then((response) => {
// console.log(response);
this.estates = response.data.data;
//some other codes....
//some other codes....
},
computed: {
//some other functions in computed...
//
initMap: function(){
var options =
{
zoom : 6,
center : {
lat:34.652500,
lng:135.506302
}
};
var map = new google.maps.Map(document.getElementById(this.mapName), options);
var marker = new google.maps.Marker({
map: map,
icon: 'imgs/marker.png',
url: "/pages/estates.id",
label: {
text: this.estates.price,
color: "#fff",
},
position: {
lat: this.estates.lat,
lng: this.estates.lng
}
});
google.maps.event.addListener(marker, 'click', function () {
window.location.href = this.url;
});
}
<div id="map"></div>
and last marker url id bind is in controller like this,
public function details($id)
{
$estates = allestates::where('id', $id)->first();
return view('pages.details', compact('estates'));
}
Do I missing something in Vue js? Thank you!
From our discussion in the comments, I realise that your issue is because this.estates is still not defined when initMap() is executed. Remember that you are using an asynchronous operation (via axios) to populate this.estates, so it is undefined at runtime. What you can do is:
Keep the map initialisation logic in initMap()
Move all the Google Map marker creation until after the axios promise has been resolved. You can abstract all that into another method, e.g. insertMarkers()
Also, remember that you need to define estates in the app/component data, otherwise it will not be reactive.
Here is an example:
data() {
return {
mapName: "map",
// Create the estate object first, otherwise it will not be reactive
estates: {}
}
},
mounted() {
this.fetchEstates();
this.initMap();
},
methods: {
fetchEstates: function(page = 1) {
axios.get('/ajax', {
params: {
page
}}).then((response) => {
this.estates = response.data.data;
// Once estates have been populated, we can insert markers
this.insertMarkers();
//pagination and stuff...
});
},
// Iniitialize map without creating markers
initMap: function(){
var mapOptions =
{
zoom : 6,
center : {
lat:34.652500,
lng:135.506302
}
};
var map = new google.maps.Map(document.getElementById(this.mapName), mapOptions);
},
// Helper method to insert markers
insertMarkers: function() {
var marker = new google.maps.Marker({
map: map,
icon: 'imgs/marker.png',
url: "/pages/estates.id",
label: {
text: this.estates.price,
color: "#fff",
},
position: {
lat: this.estates.lat,
lng: this.estates.lng
}
});
google.maps.event.addListener(marker, 'click', function () {
window.location.href = this.url;
});
}
},
Update: It also turns out that you have not addressed the issue of the data structure of this.estates. It appears that you are receiving an array from your endpoint instead of objects, so this.estates will return an array, and of course this.estates.lat will be undefined.
If you want to iterate through the entire array, you will have to use this.estates.forEach() to go through each individual estates while adding the marker, i.e.:
data() {
return {
mapName: "map",
// Create the estate object first, otherwise it will not be reactive
estates: {}
}
},
mounted() {
this.fetchEstates();
this.initMap();
},
methods: {
fetchEstates: function(page = 1) {
axios.get('/ajax', {
params: {
page
}}).then((response) => {
this.estates = response.data.data;
// Once estates have been populated, we can insert markers
this.insertMarkers();
//pagination and stuff...
});
},
// Iniitialize map without creating markers
initMap: function(){
var mapOptions =
{
zoom : 6,
center : {
lat:34.652500,
lng:135.506302
}
};
var map = new google.maps.Map(document.getElementById(this.mapName), mapOptions);
},
// Helper method to insert markers
insertMarkers: function() {
// Iterate through each individual estate
// Each estate will create a new marker
this.estates.forEach(estate => {
var marker = new google.maps.Marker({
map: map,
icon: 'imgs/marker.png',
url: "/pages/estates.id",
label: {
text: estate.price,
color: "#fff",
},
position: {
lat: estate.lat,
lng: estate.lng
}
});
google.maps.event.addListener(marker, 'click', function () {
window.location.href = this.url;
});
});
}
},
From what I can see in the screenshot you posted, this.estates is an array of objects? If that's the case you need to iterate through the array using forEach
this.estates.forEach((estate, index) => {
console.log(estate.lat);
//handle each estate object here
});
or use the first item in the array like so this.estates[0].lat, if you're only interested in the first item.

How to open map drawn from a leaflet class as a pop up window or in a new tab?

I have the following lines of code that generates a map, as seen it makes use of leaflet class to render it. Which works just fine, except that I additionally require the map to open as a new pop up window, or in a new tab on clicking anywhere on the map.
Code:-
<script src="https://unpkg.com/leaflet#1.0.1/dist/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.0/leaflet.draw.js"></script>
/* miscellaneous stuff here */
<div class="col-sm-6 col-sm-offset-4">
<leaflet class="showMap" defaults="mappingConfig.defaults" center="mappingConfig.cityCenter" markers="markers" controls="controls"></leaflet>
</div>
How should i go about achieving the same? I've not come across any relevant code examples online, which were helpful to this particular scenario
If, in the template of the modal you have a map with the same id of the map in the main view, and put in a services the map object (to share it between the controllers), you can have same objects in modal and in the view.
angular.module('mymap.services', []).factory('MapServices', function () {
var map ={
center : {
lat: 49,
lng: 34,
zoom: 8
}, defaults : {
zoomControl: false,
attributionControl: true
},
baselayers : {
xyz: {....},
markers:[....]
};
return {
getMap: function () {
return map;
},
}});
Then you can use somethings like:
$scope.$on('leafletDirectiveMarker.map.click', function (event, args) {
$scope.map.center.lat = args.model.lat;
$scope.map.center.lng = args.model.lng;
$scope.valueModal = {};
$scope.valueModal.properties = args.model.properties.properties;
//show modal
$scope.modalPopup.show();
});
Or instead to use markers into the angular-leaflet directive you can create a layer:
leafletData.getMap("map").then(function (map) {
map.invalidateSize();
//resp is the geojson
var geojson = new L.GeoJSON(resp, {
onEachFeature: function (feature, layer) {
layer.on('click', function (e) {
$scope.map.center.lat = feature.geometry.coordinates[1];
$scope.map.center.lng = feature.geometry.coordinates[0];
$scope.feature = feature;
//open a modal
$scope.openLayersModal();
});
}
});
markers.addLayer(geojson);
map.addLayer(markers);
}, function (err) {
console.log('ERROR', err.status);
});
});

Add an ImageLayer with ionic-leafletjs

I want to add an ImageLayer with the Ionic-leafletjs from calendee. I have the map obj. in the angular $scope
Markers, center and so on are working.
This is not working:
$scope.map = {
layers: {
imageOverlay: {
1: {
imageUrl: 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
imageBounds: [[47.062319, 7.614106], [47.062351, 7.614432]]
}
}
}
This is the ionic-leafletjs-map-demo which I used as ground.

gmap3 remove event listener

I want to remove event listener for click added by:
var events = {
click: function () {
// crazy stuff here :- )
}
};
$(where).gmap3(
{
events: events
}
);
Need something like:
$(where).gmap3().removeEventListener('click');
Didn't realize gmap3 was a wrapper library. Ill remove the duplicate comment.
Browsing through the gmaps3 documentation, I did not see anything specific to remove listeners with the libraries functions, but you can grab the marker with action: 'get' and then clear the listener.
Here is a example that a altered from the documentation. I added a name and tag property to the markers and at the end of this script I remove the mouseover listener from the marker with tag:'2'. For some reason this library is fickle and wants both the name and tag property to find the marker.
$('#test').gmap3({
action: 'init',
options: {
center: [46.578498, 2.457275],
zoom: 5
}
}, {
action: 'addMarkers',
markers: [
{
name : 'marker',
tag: '1',
lat: 48.8620722,
lng: 2.352047,
data: 'Paris !'},
{
name : 'marker',
tag: '2',
lat: 46.59433,
lng: 0.342236,
data: 'Poitiers : great city !'},
{
name : 'marker',
tag: '3',
lat: 42.704931,
lng: 2.894697,
data: 'Perpignan ! GO USAP !'}
],
marker: {
options: {
draggable: false
},
events: {
mouseover: function(marker, event, data) {
var map = $(this).gmap3('get'),
infowindow = $(this).gmap3({
action: 'get',
name: 'infowindow'
});
if (infowindow) {
infowindow.open(map, marker);
infowindow.setContent(data);
} else {
$(this).gmap3({
action: 'addinfowindow',
anchor: marker,
options: {
content: data
}
});
}
},
mouseout: function() {
var infowindow = $(this).gmap3({
action: 'get',
name: 'infowindow'
});
if (infowindow) {
infowindow.close();
}
}
}
}
});
//get the marker by name and tag
var mark = $('#test').gmap3({
action: 'get',
name:'marker',
tag: '2'
});
//remove the event listener
google.maps.event.clearListeners(mark, 'mouseover');
Here is an example of this script working: http://jsfiddle.net/5GcP7/. The marker in the middle will not open an infowindow when moused over.
I solved this problem in different way:
var events = {
click: function () {
if (P.settings.mapPinActive === false) {
return;
}
// crazy stuff here :- )
}
};
Instead of detaching and attaching events, global properties in settings object.

Categories

Resources