Here Maps Add Info bubble to map when using Vue - vuejs2

Trying to add info bubble to map in my heremap vue component (ive taken bits from https://developer.here.com/blog/showing-a-here-map-with-the-vue.js-javascript-framework and also https://developer.here.com/blog/develop-a-cross-platform-desktop-maps-application-with-electron-vue.js-and-here)
I have a couple of methods on my component(mostly copied over from the here docs)
methods:{
AddMarkerToGroup(group, location, icon) {
var marker = new H.map.Marker({ lat: location.Latitude, lng: location.Longitude }, { icon: icon });
marker.setData(location.Data);
group.addObject(marker);
},
addMarkersToMap(locations,defaultIconUrl) {
var scale = window.devicePixelRatio;
var icon = new H.map.Icon(defaultIconUrl, { size: { w: 45 * scale, h: 50 * scale } });
var group = new H.map.Group();
this.map.addObject(group);
group.addEventListener('tap', function (evt) {
// event target is the marker itself, group is a parent event target
// for all objects that it contains
var bubble = new H.ui.InfoBubble(evt.target.getPosition(), {
// read custom data
content: evt.target.getData()
});
// show info bubble
this.ui.addBubble(bubble);
}, false);
var addmarker = this.AddMarkerToGroup;
locations.forEach(function (location) {
addmarker(group, location, icon);
});
}`
However i cant get the info bubble to display when the map marker is clicked. this.ui is undefined in the context of this event listener. Outside the event listener it isn't undefined. ui is defined in the mounted component event:
mounted: function() {
// Initialize the platform object:
var pixelRatio = window.devicePixelRatio || 1;
let defaultLayers = this.platform.createDefaultLayers({
tileSize: pixelRatio === 1 ? 256 : 512,
ppi: pixelRatio === 1 ? undefined : 320
});
this.map = new H.Map(
this.$refs.map,
defaultLayers.normal.map,
{pixelRatio: pixelRatio, zoom: 5, center: { lat: 54.00366, lng: -2.547855} });
let behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(this.map));
this.ui = H.ui.UI.createDefault(this.map, defaultLayers);
this.LoadMapLocations();
},
Does anybody know how to get info bubble to display?

These blogs were really useful:
https://developer.here.com/blog/showing-a-here-map-with-the-vue.js-javascript-framework
https://developer.here.com/blog/develop-a-cross-platform-desktop-maps-application-with-electron-vue.js-and-here
https://developer.here.com/blog/searching-for-points-of-interest-with-the-here-places-api-in-a-vue.js-application
My problem was I forgot to add the reference to the stylesheet.
<link rel="stylesheet" type="text/css" href="https://js.api.here.com/v3/3.0/mapsjs-ui.css?dp-version=1533195059" />
dont forget to add the script files:
<script src="https://js.api.here.com/v3/3.0/mapsjs-core.js" type="text/javascript" charset="utf-8"></script>
<script src="https://js.api.here.com/v3/3.0/mapsjs-service.js" type="text/javascript" charset="utf-8"></script>
<script src="https://js.api.here.com/v3/3.0/mapsjs-places.js" type="text/javascript" charset="utf-8"></script>
<script src="https://js.api.here.com/v3/3.0/mapsjs-mapevents.js" type="text/javascript" charset="utf-8"></script>
<script src="https://js.api.here.com/v3/3.0/mapsjs-ui.js" type="text/javascript" charset="utf-8"></script>
My HereMap.vue component in full:
`<template>
<div class="here-map">
<div ref="map" v-bind:style="{ width: width, height: height }"></div>
</div>
</template>`
<script>
export default {
name: "HereMap",
data() {
return {
map: {},
platform: {},
router:{},
geocoder:{},
directions:[],
ui: null
}
},
props: {
appId: String,
appCode: String,
lat: String,
lng: String,
width: String,
height: String
},
created: function() {
this.platform = new H.service.Platform({
"app_id": this.appId,
"app_code": this.appCode,
'useHTTPS': true,
'useCIT': true
});
this.router = this.platform.getRoutingService();
this.geocoder = this.platform.getGeocodingService();
},
mounted: function() {
// Initialize the platform object:
var pixelRatio = window.devicePixelRatio || 1;
let defaultLayers = this.platform.createDefaultLayers({
tileSize: pixelRatio === 1 ? 256 : 512,
ppi: pixelRatio === 1 ? undefined : 320
});
this.map = new H.Map(
this.$refs.map,
defaultLayers.normal.map,
{pixelRatio: pixelRatio, zoom: 5, center: { lat: 54.00366, lng: -2.547855} });
let behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(this.map));
this.ui = H.ui.UI.createDefault(this.map, defaultLayers);
this.LoadMapLocations();
},
methods:{
AddMarkerToGroup(group, location, icon) {
console.log(location);
var marker = new H.map.Marker({ lat: location.Latitude, lng: location.Longitude }, { icon: icon });
marker.setData(location.Data);
group.addObject(marker);
},
addMarkersToMap(locations,defaultIconUrl) {
var scale = window.devicePixelRatio;
var icon = new H.map.Icon(defaultIconUrl, { size: { w: 45 * scale, h: 50 * scale } });
var group = new H.map.Group();
this.map.addObject(group);
var self = this;
var position;
group.addEventListener('tap', function (evt) {
position = evt.target.getPosition();
// event target is the marker itself, group is a parent event target
// for all objects that it contains
var bubble = new H.ui.InfoBubble(evt.target.getPosition(), {
// read custom data
content: evt.target.getData()
});
// show info bubble
self.ui.addBubble(bubble);
}, false);
var addmarker = this.AddMarkerToGroup;
locations.forEach(function (location) {
addmarker(group, location, icon);
});
},
LoadMapLocations(){
let locations = [
{ Name: "Wolverhampton" , Latitude:52.5914143, Longitude: -2.1496674, Data: "wolverhampton meeting" },
{ Name: "London" , Latitude:51.5048147, Longitude: -0.121162, Data: "london meeting" },
{ Name: "Manchester" , Latitude:53.4757539, Longitude: -2.2791187, Data: "manchester meeting" }
];
this.addMarkersToMap(locations,"https://image.flaticon.com/icons/png/512/33/33622.png");
},
ZoomToLocation(lat,long,zoom){
console.log("zoom to location ");
this.map.setCenter({ lat: lat, lng: long });
this.map.setZoom(zoom);
}
}
};

Related

HTML5 canvas artifacts

I have created a Vue component that loads an image into a canvas and allows the user to zoom-in and zoom-out with the mouse wheel. The image resolution is 1260x1800, the canvas resolution is 427x610 (the aspect ratio is preserved).
<template>
<canvas
ref="canvas"
:width="width"
:height="height"
#mousedown.prevent.stop="onMouseDown"
#mousemove.prevent.stop="onMouseMove"
#mouseup.prevent.stop="onMouseUp"
#mouseout.prevent.stop="onMouseOut"
#mousewheel.prevent.stop="onMouseWheel"
/>
</template>
<script>
export default {
name: 'MyZoomingCanvas',
props: {
src: {
type: String,
required: true
},
zoom: {
type: Object,
required: true,
validator: zoom =>
zoom &&
zoom.hasOwnProperty('min') &&
zoom.hasOwnProperty('max') &&
zoom.hasOwnProperty('step') &&
zoom.hasOwnProperty('scale')
}
},
data() {
return {
img: null,
isMouseDown: false,
startX: null,
startY: null,
dx: 0,
dy: 0,
width: 427,
height: 610
}
},
watch: {
'zoom.scale'() {
this.draw()
}
},
methods: {
draw() {
const canvas = this.$refs.canvas
const ctx = canvas.getContext('2d')
ctx.clearRect(0, 0, this.width, this.height)
ctx.setTransform(
this.zoom.scale,
0,
0,
this.zoom.scale,
((1 - this.zoom.scale) * this.width) / 2,
((1 - this.zoom.scale) * this.height) / 2
)
ctx.drawImage(this.img, 0, 0, this.width, this.height)
},
loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image()
img.addEventListener('load', () => resolve(img))
img.addEventListener('error', err => reject(err))
img.src = src
})
},
onMouseDown({ clientX, clientY }) {
this.startX = clientX
this.startY = clientY
this.isMouseDown = true
},
onMouseMove({ clientX, clientY }) {
if (this.isMouseDown) {
this.dx += clientX - this.startX
this.dy += clientY - this.startY
this.draw()
this.startX = clientX
this.startY = clientY
}
},
onMouseUp() {
this.isMouseDown = false
this.dx = 0
this.dy = 0
},
onMouseOut() {
this.isMouseDown = false
},
onMouseWheel({ offsetX, offsetY, deltaY }) {
this.dx = -offsetX
this.dy = -offsetY
this.$emit(
'scale-change',
Math.min(this.zoom.max, Math.max(this.zoom.min, this.zoom.scale - deltaY * this.zoom.step))
)
}
},
mounted() {
this.loadImage(this.src).then(img => {
this.img = img
this.$nextTick(() => {
this.draw()
})
})
}
}
</script>
I noticed that in some cases the images are affected by strange artifacts, take a look at this example: https://jsfiddle.net/lmartini/b3hLr5ej/latest/
In the example, while zooming-in, I can clearly see a bunch of horizontal bands along the whole fabric height that disappear after a certain zoom level.
By googling I believe that these artifacts are caused by the resolution mismatch between the canvas and the image (the so called pixel-perfect problem) and, hence, by the internal browser downsampling algorithm.
I tried to improve the image smoothing quality of the canvas by adding the following lines but they didn't make any difference at all (my target browser is Chrome):
// ctx is the canvas context
ctx.imageSmoothingEnabled = true
ctx.imageSmoothingQuality = 'high'
How can I get rid of these artifacts?

set data of parent component in child

In parent component, send a variable as prop to child(DirectionsRenderer)
In child there is function(preparePoints function in DirectionsRenderer.js) set this;
but couldnt access 'this' reference inside that function
Parent:
<template>
<div>
<div>
<h2>Start</h2>
<label>
<gmap-autocomplete #place_changed="setStartPlace"></gmap-autocomplete>
<button #click="addStartMarker">Add</button>
</label>
<br />
</div>
<div>
<h2>End</h2>
<label>
<gmap-autocomplete #place_changed="setEndPlace"></gmap-autocomplete>
<button #click="addEndMarker">Add</button>
</label>
<br />
</div>
<br />
<gmap-map ref="xyz" :center="center" :zoom="7" style="width:100%; height: 400px;">
<gmap-marker
:key="index"
v-for="(m, index) in markers"
:position="m.position"
#click="center=m.position"
></gmap-marker>
<DirectionsRenderer
:v-model="pointList" //this is what I want filled by child component
travelMode="DRIVING"
:origin="origin"
:destination="destionation"
/>
{{pointList}}
</gmap-map>
</div>
</template>
<script>
import DirectionsRenderer from "./DirectionsRenderer.js";
export default {
components: {
DirectionsRenderer,
},
name: "GoogleMap",
data() {
return {
center: { lat: 41.85, lng: -87.65 },
pointList: [],
markers: [],
places: [],
path: [],
currentPlace: null,
startPoint: {},
endPoint: {},
};
},
computed: {
origin() {
if (!this.startPoint) return null;
return this.startPoint;
},
destionation() {
if (!this.endPoint) return null;
return this.endPoint;
},
},
mounted() {
this.geolocate();
},
methods: {
getPoints() {
return this.pointList;
},
setStartPlace(place) {
this.currentPlace = place;
},
setEndPlace(place) {
this.currentPlace = place;
},
addStartMarker() {
if (this.currentPlace) {
const marker = {
lat: this.currentPlace.geometry.location.lat(),
lng: this.currentPlace.geometry.location.lng(),
};
this.startPoint = marker;
this.markers[0] = { position: marker };
this.places.push(this.currentPlace);
this.center = marker;
this.currentPlace = null;
}
},
addEndMarker() {
if (this.currentPlace) {
const marker = {
lat: this.currentPlace.geometry.location.lat(),
lng: this.currentPlace.geometry.location.lng(),
};
this.endPoint = marker;
this.markers[1] = { position: marker };
this.places.push(this.currentPlace);
this.center = marker;
this.currentPlace = null;
}
},
geolocate: function () {
navigator.geolocation.getCurrentPosition((position) => {
this.center = {
lat: position.coords.latitude,
lng: position.coords.longitude,
};
});
},
},
};
</script>
Child(DirectionsRenderer.js):
export default MapElementFactory({
name: "directionsRenderer",
ctr() {
return window.google.maps.DirectionsRenderer;
},
events: [],
mappedProps: {},
props: {
pointList: { type: Array },
origin: { type: Object },
destination: { type: Object },
travelMode: { type: String }
},
methods: {
preparePoints: (array) => {
var result = []
array.forEach(element => {
result.push({ lat: element.lat(), lng: element.lng() })
});
debugger;
this.pointList = result;//**Throws exception cant read 'pointList' of undefined
}
},
afterCreate(directionsRenderer) {
let directionsService = new window.google.maps.DirectionsService();
this.$watch(
() => [this.origin, this.destination, this.travelMode],
() => {
let { origin, destination, travelMode } = this;
if (!origin || !destination || !travelMode) return;
var self = this;
directionsService.route(
{
origin,
destination,
travelMode
},
(response, status) => {
self.preparePoints(response.routes[0].overview_path);
}
);
}
);
},
});
How to retrieve data from child properly?
There is array in child all I need to do set 'pointList' in child and use it in parent.
You cannot use events.
The problem is, that you do not have a child / parent relationship, but a deeper hierarchy. You can use provide/inject for this.
In your parent, use
export default {
... all your old code ...
provide() {
return {
// Notice that this is a lambda => this gets bound to the this in provide, which is the vue instance
pointsCallback: (points) => this.pointList = points
}
}
}
In your child, use this:
export default {
inject: ['pointsCallback'],
// Your other code ...
// { {
// Somewhere after self.preparePoints(response.routes[0].overview_path);
this.pointsCallback(theNewPointsListYouComputed);
// This will call the pointsCallback you defined in your parent.
// } } and so on
}

Vue google Map Info window

i have a vue app that showing google map by using vue2-google-map.
but i have a problem with to implement maps.infowindow to my marker because there are lack of vuejs stuff reference source.
this is my code for marker template :
<GmapMap ref="mapRef"
:center="{lat: 3.974341, lng: 102.438057}"
:zoom="7"
class="gmap"
>
<GmapMarker
:key="index"
v-for="(location, index) in markers"
:position="location"
:draggable="true"
icon="https://img.icons8.com/color/48/000000/map-pin.png"
/>
</GmapMap>
this is the script:
export default {
data() {
return {
markers: [],
infowindow: [],
};
},
async setMarker() {
const { data } = await LocationRepository.getData(); //get data from api
this.tempLatLong = data;
this.$refs.mapRef.$mapPromise.then((map) => {
this.markers = [];
this.infowindow = [];
const bounds = new this.google.maps.LatLngBounds();
for (let i = 0; i < this.tempLatLong.length; i += 1) {
const lati = parseFloat(this.tempLatLong[i].latitude);
const long = parseFloat(this.tempLatLong[i].longitude);
const location = new this.google.maps.LatLng(lati, long);
bounds.extend(location);
const marker =
{
lat: lati, lng: long
}
this.markers.push(marker);
//this is where the problem occur.
const content = '<div id="content">'+'<p>test</p>' +'</div>'
this.infowindow.push(content)
}
map.fitBounds(bounds);
map.panTo({ lat: 3.974341, lng: 102.438057 });
});
},
i referring to google map documentation about the infowindows but don't have any idea how to implement it into this code. can someone teach me how to use this infowindow in vuejs map.
here i have a working example from one of my projects. the data for the lat an lng comes from veux store. so you have to modify these
<template>
<GmapMap
:center="getCenterPosition"
:zoom="getZoomLevel"
map-type-id="roadmap"
style="width: 100%; height: 600px"
>
<GmapMarker
v-for="(m, index) in loadedDealers"
:key="index"
:position="{ lat: m.lat, lng: m.lng }"
:clickable="true"
:draggable="false"
#click="openInfoWindowTemplate(index)"
:icon="{ url: require('./test.png') }"
/>
<gmap-info-window
:options="{
maxWidth: 300,
pixelOffset: { width: 0, height: -35 }
}"
:position="infoWindow.position"
:opened="infoWindow.open"
#closeclick="infoWindow.open=false">
<div v-html="infoWindow.template"></div>
</gmap-info-window>
</GmapMap>
<script>
import { mapGetters } from 'vuex'
export default {
data() {
return {
infoWindow: {
position: {lat: 0, lng: 0},
open: false,
template: ''
}
}
},
computed: {
...mapGetters([
'getDealers',
'getCenterPosition',
'getZoomLevel',
]),
loadedDealers() {
return this.getDealers
}
},
methods: {
openInfoWindowTemplate(index) {
const { lat, lng, name, street, zip, city } = this.loadedDealers[index]
this.infoWindow.position = { lat: lat, lng: lng }
this.infoWindow.template = `<b>${name}</b><br>${street}<br>${zip} ${city}<br>`
this.infoWindow.open = true
},
}
}

Can I get return value from vue custom directives?

I have writen custom directive that make a google map.
Vue.directive('gmap', {
inserted: function (el) {
return new google.maps.Map(el, {
center: { lat: -34.397, lng: 150.644 },
zoom: 8
})
}
})
<div v-gmap ref="map" style="height: 360px"></div>
It works and I can see the map.
And then, I want to draw a marker on the map, and need a google maps object for it.
Can I get return value from v-gmap directives?
mounted () {
const map = this.$refs.map
const marker = new google.maps.Marker({
position: { lat: -34.397, lng: 150.644 },
map: map,
title: 'Hello World!'
});
}
It doesn't works.
map is just HTML element..
You should create a component instead of a directive for this:
Vue.component('gmap', {
template: '<div/>',
mounted() {
this.map = new google.maps.Map(this.$el, {
center: { lat: -34.397, lng: 150.644 },
zoom: 8,
});
const marker = new google.maps.Marker({
map: this.map,
position: { lat: -34.397, lng: 150.644 },
title: 'Hello World!',
});
},
});
<gmap style="height: 360px"/>
This is just a start, of course. You can pass in markers through a prop if you want the markers to be controlled externally from the component; it's up to you how you want it to function.

Intermittent Error using Google Maps API: Uncaught TypeError: undefined is not a function

Hi All: a little help needed...
I am trying to use Google Maps API now and I followed all the instructions from the official google site for it. However, I noticed that it doesn't always work.
Often times, I get an error:
Failed to load resource: net::ERR_CONNECTION_REFUSED https://maps.gstatic.com/intl/en_us/mapfiles/api-3/16/8/main.js
Uncaught TypeError: undefined is not a function
I sometimes refresh and it works. But sometimes this error appears in the console.
Is this an issue with the API or my code?
Here's my jsfiddle: http://jsfiddle.net/SQ3Bx/
<script>var myLatlng = new google.maps.LatLng(14.572388, 121.057784);
var marker;
var map;
function initialize() {
var styles = [
{
stylers: [
{ hue: "#319885" },
]
},
{
featureType: 'water',
elementType: 'geometry.fill',
stylers: [
{ color: '#3191fe' }
]
}
];
var styledMap = new google.maps.StyledMapType(styles,
{name: "Timeframe Style"});
var isDraggable = $(document).width() > 1000 ? true : false;
var mapOptions = {
draggable: isDraggable,
center: myLatlng,
zoom: 14,
scrollwheel: false,
mapTypeControlOptions: {
mapTypeIds: [google.maps.MapTypeId.SATELLITE, 'map_style']
}
};
map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
image = 'img/map_marker.png';
marker = new google.maps.Marker({
map: map,
draggable: true,
animation: google.maps.Animation.DROP,
position: myLatlng,
icon: image
});
map.mapTypes.set('map_style', styledMap);
map.setMapTypeId('map_style');
google.maps.event.addListener(marker, 'click', toggleBounce);
}
function toggleBounce() {
if (marker.getAnimation() != null) {
marker.setAnimation(null);
} else {
marker.setAnimation(google.maps.Animation.BOUNCE);
}
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>