We're building an app using Ionic 6 / Vue 3 / Capacitor as our framework. On one of the pages we need to display a form which, among other inputs, contain 2 map components. The user can tap to pinpoint a geographical location in each of the maps. This is our map component:
<template>
<ion-item>
<ion-label position="stacked" color="tertiary" style="margin-bottom: 10px"
>{{ controlLabel }} (Tap to choose position)</ion-label
>
<div class="mapBox">
<div
:id="'map'+randomId"
class="map"
#click="getCoord($event)"
></div>
</div>
<div>
<ion-label color="tertiary" position="stacked">{{
$lang.Lengdegrad
}}</ion-label>
<ion-input
v-model="lon"
#change="setMarker($event)"
:controlIdLat="controlIdLat"
:data-value="valueLat"
/>
</div>
<div>
<ion-label color="tertiary" position="stacked">{{
$lang.Breddegrad
}}</ion-label>
<ion-input
v-model="lat"
#change="setMarker($event)"
:controlIdLon="controlIdLon"
:data-value="valueLon"
/>
</div>
</ion-item>
</template>
<script>
import { defineComponent } from "#vue/runtime-core";
import { IonInput, IonItem, IonLabel } from "#ionic/vue";
import Map from "ol/Map";
import View from "ol/View";
import Feature from "ol/Feature";
import Point from "ol/geom/Point";
import { Style, Icon } from "ol/style";
import { OSM, Vector as VectorSource } from "ol/source";
import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer";
import { fromLonLat, toLonLat } from "ol/proj";
import XYZ from "ol/source/XYZ";
import "ol/ol.css";
import PinImg from "../../resources/icons8-pin-48.png";
import { Geolocation } from "#capacitor/geolocation";
export default defineComponent({
props: [
"controlLabel",
"controlIdLat",
"controlIdLon",
"lonLatFields",
"valueLat",
"valueLon",
"setStartMarker",
],
components: {
IonInput,
IonItem,
IonLabel,
},
data() {
return {
mainMap: null,
lat: null,
lon: null,
pinLayer: null,
pinFeat: null,
$lang: this.$lang,
isOnline: this.$isOnline,
isVisible: false,
randomId: Math.random().toString(36).substr(2, 5),
};
},
mounted() {
console.log("randomid",this.randomId)
this.source = new VectorSource();
setTimeout(async () => {
if (document.readyState === "loading") {
document.addEventListener(
"DOMContentLoaded",
await this.getLocation()
);
} else {
await this.getLocation();
}
this.myMap();
this.$nextTick(() => {
if (this.setStartMarker) {
this.setMarker();
}
view.setCenter(fromLonLat([this.lon, this.lat]));
});
}, 100);
},
methods: {
async getLocation() {
const position = await Geolocation.getCurrentPosition();
this.lat = position.coords.latitude.toFixed(3);
this.lon = position.coords.longitude.toFixed(3);
},
getCoord(event) {
let lonlat = toLonLat(this.mainMap.getEventCoordinate(event));
this.lat = lonlat[1].toFixed(3);
this.lon = lonlat[0].toFixed(3);
this.$nextTick(() => {
this.setMarker();
});
},
myMap() {
this.mainMap = new Map({
layers: [
new TileLayer({
source: new OSM(),
}),
new TileLayer({
source: new XYZ({
url: "https://opencache.statkart.no/gatekeeper/gk/gk.open_gmaps?layers=sjokartraster&zoom={z}&x={x}&y={y}",
attributions:
'Kartverket',
}),
}),
(this.pinLayer = new VectorLayer({
source: new VectorSource({
features: [],
}),
style: new Style({
image: new Icon({
anchor: [0.5, 46],
anchorXUnits: "fraction",
anchorYUnits: "pixels",
src: PinImg,
}),
}),
})),
],
target: "map" + this.randomId,
view: view,
});
console.log("map", this.mainMap);
},
setMarker() {
let p = new Point(fromLonLat([this.lon, this.lat]));
console.log("setmarker", p);
if (!this.pinLayer.getSource().getFeatures().length) {
this.pinFeat = new Feature({
geometry: p,
});
this.pinLayer.getSource().addFeature(this.pinFeat);
} else {
this.pinFeat.setGeometry(p);
}
let vals = {
[this.controlIdLat]: this.lat,
[this.controlIdLon]: this.lon,
};
this.$emit("input", vals);
console.log(p.getCoordinates());
view.setCenter(p.getCoordinates());
},
},
});
const view = new View({
center: fromLonLat([13.486, 68.131]),
zoom: 10,
constrainResolution: true,
});
</script>
<style scoped>
div.map {
border: 5px solid white;
margin: 0 auto;
height: 100%;
width: 100%;
}
div.mapBox {
width: 100%;
height: 40vh;
}
</style>
Testing in the browser, this kind of works. The user can tap each of the two maps and two separate locations will be saved to the variables. However, when panning or zooming one of the maps, the other one follows so that they both show exactly the same map area.
As you can see, I've tried assigning a random id to each map to separate them from each other. That didn't work.
Testing on an Android phone, only the latter of the two maps are displayed. The first mainMap is left undefined.
My guess is that if I can figure out the first nuisance, then the second problem will also be solved. Any tips?
Edit: I tried making a new identical map component and use one for each map. Now the maps work as expected in the browser, being controlled separately. However the first map is still undefined on Android.
Related
I am trying to build a map application with the vuemapbox library , but the strange thing is , all the markers are showing at the bottom of the page which is strange , can any one help me with it ?
<template>
<div class="home">
<MglMap :accessToken="accessToken" :mapStyle="mapStyle" #load="onMapLoaded">
<MglMarker
:coordinates.sync="locations"
color="blue"
v-for="(location, index) in pins"
:key="index"
/>
</MglMap>
</div>
</template>
<script>
// # is an alias to /src
import Mapbox from "mapbox-gl";
import {
MglMap,
MglMarker
} from "vue-mapbox";
export default {
name: "Home",
components: { MglMap, MglMarker },
data() {
return {
accessToken:"xxx",
mapStyle: "mapbox://styles/mapbox/streets-v11",
pins: [
[20.341979525348204, 85.8345150468384],
[25.581627613058714, 80.87795332144299],
[25.199370930176993, 95.86881932189225],
],
};
},
beforeMount() {
this.$store.dispatch("set_user_location");
},
computed: {
locations() {
return this.$store.getters.getThisUserMarker;
},
},
created() {
this.mapbox = Mapbox;
},
methods: {
async onMapLoaded(event) {
// in component
this.map = event.map;
const asyncActions = event.component.actions;
await asyncActions.flyTo({
center: this.locations,
zoom: 4,
speed: 1,
});
},
},
};
</script>
This is what i have done so far , but having this strange issue , please help , Every help is appreciated .
Make shure that you have loaded mabpox css.
Try to set height for MglMap
<MglMap
width="100%"
height="100%"
...
/>
I have a <b-modal> from VueBootstrap, inside of which I'm trying to render a <GmapMap> (https://www.npmjs.com/package/gmap-vue)
It's rendering a grey box inside the modal, but outside the modal it renders the map just fine.
All the searching I've done leads to the same solution which I'm finding in some places is google.maps.event.trigger(map, 'resize') which is not working. Apparently, it's no longer part of the API [Source: https://stackoverflow.com/questions/13059034/how-to-use-google-maps-event-triggermap-resize]
<template>
<div class="text-center">
<h1>{{ title }}</h1>
<div class="row d-flex justify-content-center">
<div class="col-md-8">
<GmapMap
ref="topMapRef"
class="gmap"
:center="{ lat: 42, lng: 42 }"
:zoom="7"
map-type-id="terrain"
/>
<b-table
bordered
dark
fixed
hover
show-empty
striped
:busy.sync="isBusy"
:items="items"
:fields="fields"
>
<template v-slot:cell(actions)="row">
<b-button
size="sm"
#click="info(row.item, row.index, $event.target)"
>
Map
</b-button>
</template>
</b-table>
<b-modal
:id="mapModal.id"
:title="mapModal.title"
#hide="resetInfoModal"
ok-only
>
<GmapMap
ref="modalMapRef"
class="gmap"
:center="{ lat: 42, lng: 42 }"
:zoom="7"
map-type-id="terrain"
/>
</b-modal>
</div>
</div>
</div>
</template>
<script>
// import axios from "axios";
import { gmapApi } from 'gmap-vue';
export default {
name: "RenderList",
props: {
title: String,
},
computed: {
google() {
return gmapApi();
},
},
updated() {
console.log(this.$refs.modalMapRef);
console.log(window.google.maps);
this.$refs.modalMapRef.$mapPromise.then((map) => {
map.setCenter(new window.google.maps.LatLng(54, -2));
map.setZoom(2);
window.google.maps.event.trigger(map, 'resize');
})
},
data: function () {
return {
items: [
{ id: 1, lat: 42, long: 42 },
{ id: 2, lat: 42, long: 42 },
{ id: 3, lat: 42, long: 42 },
],
isBusy: false,
fields: [
{
key: "id",
sortable: true,
class: "text-left",
},
{
key: "text",
sortable: true,
class: "text-left",
},
"lat",
"long",
{
key: "actions",
label: "Actions"
}
],
mapModal: {
id: "map-modal",
title: "",
item: ""
}
}
},
methods: {
// dataProvider() {
// this.isBusy = true;
// let promise = axios.get(process.env.VUE_APP_LIST_DATA_SERVICE);
// return promise.then((response) => {
// this.isBusy = false
// return response.data;
// }).catch(error => {
// this.isBusy = false;
// console.log(error);
// return [];
// })
// },
info(item, index, button) {
this.mapModal.title = `Label: ${item.id}`;
this.mapModal.item = item;
this.$root.$emit("bv::show::modal", this.mapModal.id, button);
},
resetInfoModal() {
this.mapModal.title = "";
this.mapModal.content = "";
},
},
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1 {
margin-bottom: 60px;
}
.gmap {
width: 100%;
height: 300px;
margin-bottom: 60px;
}
</style>
Does anyone know how to get the map to display properly in the modal?
Surely, I'm not the first to try this?
Had this problem, in my case it was solved by providing the following options to google maps:
mapOptions: {
center: { lat: 10.365365, lng: -66.96667 },
clickableIcons: false,
streetViewControl: false,
panControlOptions: false,
gestureHandling: 'cooperative',
mapTypeControl: false,
zoomControlOptions: {
style: 'SMALL'
},
zoom: 14
}
However you can probably make-do with just center and zoom.
Edit: Try using your own google maps components, follow this tutorial:
https://v2.vuejs.org/v2/cookbook/practical-use-of-scoped-slots.html#Base-Example
You can use the package described in the tutorial to load the map, dont be scared by the big red "deprecated" warning on the npm package page.
However for production, you should use the package referenced by the author, which is the one backed by google:
https://googlemaps.github.io/js-api-loader/index.html
The only big difference between the two:
The 'google' object is not returned by the non-deprecated loader, it is instead attached to the window. See my answer here for clarification:
'google' is not defined Using Google Maps JavaScript API Loader
Happy coding!
I have several scenes that I want to include in my nuxt project.
The project should have pages, and each page has 4 scenes.
I'm currently 'coding' each page and import the scenes manually.
This is my pages tree:
pages tree
This is my first page
<template>
<main class="main">
<Scene1 />
<Scene2 />
<Scene3 />
<!-- <Scene4 /> -->
</main>
</template>
<script>
import Scene1 from './1/Scene1.vue'
import Scene2 from './1/Scene2.vue'
import Scene3 from './1/Scene3.vue'
// import Scene4 from './1/Scene4.vue'
export default {
components: {
Scene1,
Scene2,
Scene3,
// Scene4
}
}
</script>
<style scoped>
.main{
display: grid;
grid-template-columns: 50% 50%;
}
</style>
And this is how a scene looks like:
<template>
<div class="three-item">
<div class="item-title">
Color Rectangle
</div>
<p>Vertex</p>
<pre><code id="vertexShader">
void main() {
gl_Position = vec4( position, 1.0 );
}
</code></pre>
<p>Fragment</p>
<pre><code id="fragmentShader">
#ifdef GL_ES
precision mediump float;
#endif
uniform float u_time;
void main() {
// Magenta (1,0,1)
gl_FragColor = vec4(1.0,0.0,1.0,1.0);
}
</code></pre>
<div id="scene1" class="scene">
</div>
</div>
</template>
<script>
import * as Three from 'three'
import hljs from 'highlight.js'
import glsl from 'highlight.js/lib/languages/glsl';
export default{
name: 'Scene1',
data() {
return {
camera: null,
scene: null,
renderer: null,
mesh: null
}
},
methods: {
init: function() {
this.container = document.getElementById('scene1');
this.camera = new Three.PerspectiveCamera(70, this.container.clientWidth/this.container.clientHeight, 0.01, 10);
this.camera.position.z = 1;
this.scene = new Three.Scene();
this.uniforms = {
u_time: { type: "f", value: 1.0 },
u_resolution: { type: "v2", value: new Three.Vector2() },
u_mouse: { type: "v2", value: new Three.Vector2() }
};
let material = new Three.ShaderMaterial( {
uniforms: this.uniforms,
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );
let geometry = new Three.BoxBufferGeometry(0.2, 0.2, 0.2);
// let material = new Three.MeshNormalMaterial();
this.mesh = new Three.Mesh(geometry, material);
this.scene.add(this.mesh);
this.renderer = new Three.WebGLRenderer({alpha:true});
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
this.container.appendChild(this.renderer.domElement);
window.addEventListener( 'resize', this.onWindowResize, false );
hljs.registerLanguage('glsl', glsl);
document.querySelectorAll('pre code').forEach((block) => {
hljs.highlightBlock(block);
});
},
animate: function() {
requestAnimationFrame(this.animate);
this.mesh.rotation.x += 0.01;
this.mesh.rotation.y += 0.02;
this.uniforms.u_time.value += 0.05;
this.renderer.render(this.scene, this.camera);
},
onWindowResize: function() {
this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
}
},
mounted() {
this.init();
this.animate();
this.onWindowResize();
}
}
</script>
To be honest, I don't get behind the dynamical created pages with nuxt. Is there a way to have a template that fetches 4 scenes, puts them into a page and does it all over again? It could be that I want to add scenes in the middle of the pages. That would destroy my current setup.
My goal is to have something like this:
dreams tree
the page.vue is crawling through the scenes, picks 4, creates a page called '1', and does it all over again. Can someone lend me a hand with that? 😊In the examples at the nuxt homepage they use .json files. I already set up all three.js scenes, but these have javascript files in it.
Erik
I'm new to Vue JS and I'm trying to make a map with marker clusters because I'm having a great number of markers in my map. I wrote a code based on the Vue2leaflet documentation, but it doesn't show the clusters and there is no error.
I tried to use l-marker,but I was struggling with it, I was having "Cannot read property 'lat' of undefined" error and even though I was really trying to resolve this problem, at the end I decided to use l-geo-json to show the markers on the map.
<template lang="html">
<section class="charts-highcharts">
<grid-block title="Map">
<div class="-map" id="map" >
<l-map :zoom="8" :center="[47.41322, -1.219482]" :options="{ gestureHandling: true }" ref="map">
<l-tile-layer url="http://{s}.tile.osm.org/{z}/{x}/{y}.png"></l-tile-layer>
<l-marker-cluster :options="clusterOptions">
<l-geo-json v-for="l in locations" :key="l.id" :geojson="l.location" :options="options"></l-geo-json>
</l-marker-cluster>
</l-map>
</div>
</grid-block>
</section>
</template>
<script lang="js">
import L from "leaflet";
import { LMap, LTileLayer, LMarker, LGeoJson, LPopup } from "vue2-leaflet";
import { GestureHandling } from "leaflet-gesture-handling";
import Vue2LeafletMarkercluster from 'vue2-leaflet-markercluster';
import 'vue2-leaflet-markercluster/dist/leaflet.markercluster.js';
import 'vue2-leaflet-markercluster/dist/leaflet.markercluster-src.js';
export default {
name: 'Maps',
components: {
LMap,
LTileLayer,
LMarker,
LGeoJson,
LPopup,
"l-marker-cluster": Vue2LeafletMarkercluster,
},
data () {
return {
geojson: null,
clusterOptions: { disableClusteringAtZoom: 11},
locations: [],
options: {
onEachFeature: function onEachFeature(feature, layer) {
layer.bindPopup(feature.properties.name);
},
},
};
},
created () {
L.Map.addInitHook("addHandler","gestureHandling", GestureHandling);
},
mounted() {
let geojson = response.data.features,
id = 0,
tmpLocations = [];
for (let l of geojson) {
tmpLocations.push({
id: id,
location: l,
});
id++;
}
this.locations = tmpLocations;
});
},
};
</script>
<style scoped lang="scss">
#import "~leaflet.markercluster/dist/MarkerCluster.css";
#import "~leaflet.markercluster/dist/MarkerCluster.Default.css";
.charts-highcharts {
}
.-map {
width: 100%;
height: 800px;
}
</style>
Do you know why the clusters aren't showing?
I want to load a simple map using Vue.js.
I created a component named map to load the basic map but somethings goes wrong and I tried many things but nothings works with me.
On my index.html I put all the javascript api from Here maps.
I am try to use this sample as a start point.
So,anyone has a Ideia what I am duing wrong?
Thanks.
https://developer.here.com/documentation/maps/topics/quick-start.html
<template>
<div>
<div id="mapContainer">
</div>
</div>
</template>
<script>
export default {
name: 'maps',
data: function() {
return {
map: null,
maptypes: null
}
},
mounted () {
this.initMap ();
},
methods: {
initMap() {
// Initialize the platform object:
var platform = new H.service.Platform({
app_id: 'nyKybJs4fZYfMCd7jfsx',
app_code: 'E_xE5837hGk33SL8M6hWIg',
useCIT: true,
useHTTPS: true
});
this.maptypes = platform.createDefaultLayers();
// Instantiate (and display) a map object:
this.map = new H.Map(
document.getElementById('mapContainer'),
this.maptypes.normal.map,
{
zoom: 10,
center: { lng: 13.4, lat: 52.51 }
});
}
}
}
</script>
<template>
<div>
<div style="width: 100%; height: 500px" id="map-container"></div>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data: () => ({ map: null }),
mounted () {
// Initialize the platform object:
const platform = new H.service.Platform({
app_id: 'nyKybJs4fZYfMCd7jfsx',
app_code: 'E_xE5837hGk33SL8M6hWIg',
useCIT: true,
useHTTPS: true,
});
const maptypes = platform.createDefaultLayers();
// Instantiate (and display) a map object:
this.map = new H.Map(
this.$el.getElementById('map-container'),
maptypes.normal.map,
{
zoom: 10,
center: { lng: 13.4, lat: 52.51 },
},
);
},
}
</script>
You should provide explicit size of map container. For example:
<template>
<div>
<div style="width: 100%; height: 500px" id="mapContainer"></div>
</div>
</template>