I am converting a project from Vue2 to Vue3 using pinia and <script setup>. The project uses vue3-openlayers.
I have my map working:
<ol-map :loadTilesWhileAnimating="true" :loadTilesWhileInteracting="true" style="height: 90%; width: 100%"
data-projection="EPSG:4326" :projection="projection">
<ol-view ref="view" :center="centerOfMap" :rotation="rotation" :zoom.sync="zoom" :projection="projection" />
<ol-tile-layer>
<ol-source-osm />
</ol-tile-layer>
<ol-vector-layer>
<ol-source-vector>
<!--This gives us the center pin -->
<ol-feature>
<ol-geom-point :coordinates="[-309812.55518814427, 6929102.511332245]"></ol-geom-point>
<ol-style>
<ol-style-circle radius="5">
<ol-style-fill color="red"></ol-style-fill>
<ol-style-stroke color="red" width="2"></ol-style-stroke>
</ol-style-circle>
<ol-style-icon :src="purplePinIcon" :anchor="[0.25, 0.9]" :scale="0.07"></ol-style-icon>
</ol-style>
</ol-feature>
. . .
</ol-source-vector>
</ol-vector-layer>
</ol-map>
and this centers the map and puts in a pin as expected. I would like to add more pins to the map which come from object in the store.
<script setup>
import { ref, computed } from 'vue'
import { useStore } from '../stores/store.js'
import * as turf from '@turf/turf'
import purplePin from '../assets/purple-pin.png'
//const props = defineProps(['ActiveEvent'])
const purplePinIcon = purplePin
const store = useStore()
const ActiveEvent = computed(() => store.ActiveEvent)
. . . more stuff including the functions geometry() and convertToMercator()
</script>
When I add the following html block to the map
<ol-feature v-for="(S, idx) in ActiveEvent.SellersList" :key="idx">
<ol-geom-point :coordinates="convertToMercator(S.Latitude, S.Longlitude)"> </ol-geom-point>
<ol-style-circle radius="5">
<!-- <ol-style-fill color="white"></ol-style-fill>
<ol-style-stroke color="red"></ol-style-stroke> -->
</ol-style-circle>
<ol-style-icon :src="purplePinIcon" :anchor="[0.25, 0.9]" :scale="0.07"></ol-style-icon>
</ol-feature>
I get a console error:
vue3-openlayers.es.js:49236 Uncaught (in promise) TypeError: Cannot read properties of null (reading 'value')
I know the ActivEvent is being read and is properly reactive because I see the values if I add the following code (outside of the ol-map)
<div v-for="(S, idx) in ActiveEvent.SellersList" :key="idx">
<h3>{{ idx }}</h3>
<pre>{{ S }} , {{ convertToMercator(S.Latitude, S.Longlitude) }}</pre>
<hr />
</div>
I have tried reading ActiveEvent from the store and passing it in as a prop from the parent component which also uses the store.ActiveEvent and neither are working for me. How do I make the ol-feature reactive, or wait for the ActiveEvent to be available before rendering. I have also tried v-if="ActiveEvent" on the ol-feature and v-cloak on the div that surrounds the map to prevent rendering until the ActiveEvent is loaded with no joy. I've been on this for 2 days now and I am sure it will be a 5 minute fix, but any help is much appreciated. For context there is nothing wrong with the geometry() or convertToMercator() functions, they both render proper data sets outside of the map. Additional: If the SellersList is an empty array, then I don't get the error. Don't know if that helps, but it definately feels like it is an issue reading the array elements.
Edits - As per requests in the comments: convertToMercator is jsut a wrapper around the turf function. My data is coming in as KML which is lat/long and turf wants long/lat.
function convertToMercator(lat, long) {
let x = turf.toMercator([long, lat])
console.log("Mercator Value: ", x)
return x
}
SellersList is just an array of lat/long coordinates
[{"Latitude":52.7122145235652,"Longlitude":-2.77611455052844},{"Latitude":52.7122145235652,"Longlitude":-2.77611455052844},{"Latitude":52.705886,"Longlitude":-2.779051},{"Latitude":52.705886,"Longlitude":-2.779051}]
I think the issue may actually be with turf and when the calculations are trying to run, rather than the mapping. This works:
<template>
<div>
<pre>{{ turf.point([parseFloat(long), parseFloat(lat)]) }}</pre>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useStore } from '../stores/store.js'
import * as turf from '@turf/turf'
const store = useStore()
const ActiveEvent = computed(() => store.ActiveEvent)
let lat = ref(52.7076467374228)
let long = ref(-2.78309353537633)
</script>
It returns
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
-2.78309353537633,
52.7076467374228
]
}
}
If I change the variables from being hard assigned to coming from a variable (in this case the store.ActiveEvent.lat)
<template>
<div>
<pre>{{ turf.point([parseFloat(long), parseFloat(lat)]) }}</pre>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useStore } from '../stores/store.js'
import * as turf from '@turf/turf'
const store = useStore()
const ActiveEvent = computed(() => store.ActiveEvent)
let lat = ref(ActiveEvent.lat) // <-- This is different
let long = ref(-2.78309353537633)
</script>
I get an error in the console
Uncaught (in promise) Error: coordinates must contain numbers
and the component tries to remount 50x a second.... I have tried
let lat = ActiveEvent.lat
let lat = computed(() => ActiveEvent.lat)
let lat = ref(ActiveEvent.lat)
I have tried setting the values in the onMounted and onBeforeMount hooks with no success. What I need is to defer the calculations until the data has loaded. Any ideas?