Skip to content

Commit

Permalink
query and compute location infos
Browse files Browse the repository at this point in the history
  • Loading branch information
mki-c2c committed Nov 19, 2024
1 parent f6bc28c commit b474156
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 26 deletions.
32 changes: 18 additions & 14 deletions src/components/info/info-panel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import SidePanelLayout from '@/components/common/side-panel-layout.vue'
import { useAppStore } from '@/stores/app.store'
import { useMapStore } from '@/stores/map.store'
import { storeToRefs } from 'pinia'
import LocationInfo from './location-info.vue'
const { t } = useTranslation()
const appStore = useAppStore()
Expand All @@ -22,21 +23,24 @@ const { locationInfo } = storeToRefs(useMapStore())
</template>

<template v-slot:content>
<div>
Coordinates:
{{ locationInfo ? locationInfo : 'no selection' }}
</div>
<template v-if="locationInfo">
<LocationInfo />
</template>

<div class="text-white">
<ul class="list-disc pl-10">
<li>
{{ t(`A right click (tap and hold on mobile)...`, { ns: 'app' }) }}
</li>
<li>
{{ t(`A short click (tap on mobile)...`, { ns: 'app' }) }}
</li>
</ul>
</div>
<template v-else>
<div class="text-white">
<ul class="list-disc pl-10">
<li>
{{
t(`A right click (tap and hold on mobile)...`, { ns: 'app' })
}}
</li>
<li>
{{ t(`A short click (tap on mobile)...`, { ns: 'app' }) }}
</li>
</ul>
</div>
</template>
</template>
</side-panel-layout>
</template>
109 changes: 109 additions & 0 deletions src/components/info/location-info.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { useTranslation } from 'i18next-vue'
import { storeToRefs } from 'pinia'
import { useMapStore } from '@/stores/map.store'
import useMap from '@/composables/map/map.composable'
import {
create_short_url,
getQRUrl,
getElevation,
getNearestAddress,
formatCoords,
INFO_PROJECTIONS,
} from '@/services/info/location-info'
import { transform } from 'ol/proj'
const { t } = useTranslation()
const { locationInfo } = storeToRefs(useMapStore())
const map = useMap().getOlMap()
const shortUrl = ref()
const elevation = ref()
const address = ref()
const clickCoordinateLuref = ref()
// const shortUrl = computed(async () => await create_short_url(locationInfo))
watch(locationInfo, async () => {
if (!locationInfo) {
return
}
shortUrl.value = await create_short_url(locationInfo.value)
clickCoordinateLuref.value = transform(
locationInfo.value!,
map.getView().getProjection(),
'EPSG:2169'
)
})
/* const clickCoordinateLuref = computed(() => locationInfo.value && transform(
* locationInfo.value, map.getView().getProjection(), 'EPSG:2169')
* )
* */
const qrUrl = computed(() => getQRUrl(shortUrl.value))
watch(clickCoordinateLuref, async () => {
elevation.value = await getElevation(clickCoordinateLuref.value!)
})
watch(clickCoordinateLuref, async () => {
address.value = await getNearestAddress(clickCoordinateLuref.value!)
})
const formatted_coordinates = computed(() =>
Object.fromEntries(
Object.entries(INFO_PROJECTIONS).map(([crs, label]) => [
label,
formatCoords(locationInfo.value, map.getView().getProjection(), crs),
])
)
)
</script>

<template>
<div>
<div>
<h3>{{ t('Short Url', { ns: 'client' }) }}</h3>
</div>
<div>
<input
onclick="this.select();"
type="text"
:value="shortUrl"
readonly="true"
/>
</div>
<div>
<img
class="img-responsive"
style="margin-top: 22px"
v-if="qrUrl"
:src="qrUrl"
/>
</div>
</div>
<div>
<div>
<h3>{{ t('Location Coordinates', { ns: 'client' }) }}</h3>
</div>
<table>
<tr v-for="(coords, label) in formatted_coordinates" :key="label">
<th style="text-align: left">{{ t(label as string) }}</th>
<td>{{ coords }}</td>
</tr>
<tr>
<th style="text-align: left">{{ t('Elevation') }}</th>
<td>{{ elevation }}</td>
</tr>
<tr>
<th style="text-align: left">{{ t('Adresse la plus proche') }}</th>
<td>{{ address?.formattedAddress }}</td>
</tr>
<tr>
<th style="text-align: left">{{ t('Distance approximative') }}</th>
<td>{{ address?.distance }}</td>
</tr>
</table>
</div>
</template>
28 changes: 16 additions & 12 deletions src/composables/map/location-info.composable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,35 +46,39 @@ export default function useLocationInfo() {
}
})

map
.getViewport()
.addEventListener('contextmenu', event => event.preventDefault())

listen(map, 'pointerdown', event =>
handleClick(event as MapBrowserEvent<PointerEvent>)
)

const setClickCordinate = (event: MapBrowserEvent<PointerEvent>) => {
const getClickCoordinate = (event: MapBrowserEvent<PointerEvent>) => {
return map.getEventCoordinate(event.originalEvent)
// TODO reproject ?
}

const handleClick = function (event: MapBrowserEvent<PointerEvent>) {
const pixel = event.pixel

if (event.originalEvent.button === 0) {
// if left mouse click
locationInfo.value = undefined
}
startPixel = event.pixel
if (event.originalEvent.button === 2) {
// if right mouse click
locationInfo.value = setClickCordinate(event)
locationInfo.value = getClickCoordinate(event)
} else if (event.originalEvent.pointerType == 'touch') {
window.clearTimeout(holdTimeoutId)
startPixel = pixel
holdTimeoutId = window.setTimeout(() => {
locationInfo.value = setClickCordinate(event)
locationInfo.value = getClickCoordinate(event)
})
}
}

listen(map, 'pointerup', () => {
listen(map, 'pointerup', (event: any) => {
if (
startPixel &&
(event as MapBrowserEvent<PointerEvent>).originalEvent.button === 0
) {
// if left mouse click
locationInfo.value = undefined
}
window.clearTimeout(holdTimeoutId)
startPixel = null
})
Expand Down
96 changes: 96 additions & 0 deletions src/services/info/location-info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { urlStorage } from '@/services/state-persistor/storage/url-storage'
import { transform } from 'ol/proj'
import { Coordinate } from 'ol/coordinate'
import { Projection } from 'ol/proj'

export const INFO_PROJECTIONS = {
'EPSG:2169': 'Luref',
'EPSG:4326': 'Lon/Lat WGS84',
'EPSG:4326:DMS': 'Lon/Lat WGS84 DMS',
'EPSG:4326:DMm': 'Lon/Lat WGS84 DM',
'EPSG:3263*': 'WGS84 UTM',
}

const HEMISPHERES = ['EW', 'NS']

export function formatCoords(
coords: Coordinate | undefined,
fromCrs: Projection,
format: string
) {
let toCrs = format
const ext = format.split(':')[2]
let utm = ''
if (ext !== undefined) {
toCrs = format.slice(0, format.lastIndexOf(':'))
}
if (format.endsWith('*')) {
const lonlat = transform(coords!, fromCrs, 'EPSG:4326')
toCrs = Math.floor(lonlat[0]) >= 6 ? 'EPSG:32632' : 'EPSG:32631'
utm = ` (UTM${toCrs.slice(-2)}N)`
}
const newCoords = transform(coords!, fromCrs, toCrs)
if (ext === undefined) {
return (
newCoords
.map((coord, i) => {
let roundedCoord = Math.round(coord)
if (format === 'EPSG:4326') {
roundedCoord = Math.round(coord * 1e6) / 1e6
}
const hemisphere =
roundedCoord > 0 ? HEMISPHERES[i][0] : HEMISPHERES[i][1]
return `${Math.abs(roundedCoord)} ${hemisphere}`
})
.join(' | ') + utm
)
}
return newCoords
.map((degrees, i) => {
const normalizedDegrees = ((degrees + 180) % 360) - 180
const hemisphere =
normalizedDegrees > 0 ? HEMISPHERES[i][0] : HEMISPHERES[i][1]
const absDegrees = Math.abs(normalizedDegrees)
const intDegrees = Math.floor(absDegrees)
const minutes = (absDegrees % 1) * 60
if (ext === 'DMm') {
const roundedMinutes = Math.round(minutes * 1e6) / 1e6
return `${intDegrees}\u00b0 ${roundedMinutes}\u2032 ${hemisphere}`
}
if (ext === 'DMS') {
const intMinutes = Math.floor(minutes)
const seconds = Math.round((minutes % 1) * 600) / 10
return `${intDegrees}\u00b0 ${intMinutes}\u2032 ${seconds}\u2033 ${hemisphere}`
}
})
.join(' | ')
}

export async function create_short_url(opt_coordinate: Coordinate | undefined) {
return (await urlStorage.getShortUrl(opt_coordinate)).short_url
}

export function getQRUrl(shortUrl: string | undefined) {
return shortUrl && `https://migration.geoportail.lu/qr?url=${shortUrl}`
}

export async function getElevation(coords: Coordinate) {
const resp = await fetch(
`https://migration.geoportail.lu/raster?lat=${coords[1]}&lon=${coords[0]}`
)
const json = await resp.json()
return `${json.dhm} m`
}

export async function getNearestAddress(coords: Coordinate) {
const resp = await fetch(
`https://migration.geoportail.lu/geocode/reverse?easting=${coords[0]}&northing=${coords[1]}`
)
const json = await resp.json()
const nearestAddress = json.results[0]
const formattedAddress = `${nearestAddress.number}, ${nearestAddress.street}, ${nearestAddress.postal_code} ${nearestAddress.locality}`
return {
formattedAddress,
distance: `${Math.round(nearestAddress.distance * 100) / 100} m`,
}
}
39 changes: 39 additions & 0 deletions src/services/state-persistor/storage/url-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,45 @@ export class UrlStorage implements Storage {
throw new Error('Method key() not implemented. ' + index)
}

getStrippedUrl(opt_coordinate: number[] | undefined) {
// stripped by embedded app parameters
const url = new URL(window.location.toString())
const params = new URLSearchParams(url.search)

if (opt_coordinate !== undefined) {
params.set('X', Math.round(opt_coordinate[0]).toString())
params.set('Y', Math.round(opt_coordinate[1]).toString())
}
params.delete('localforage')
params.delete('applogin')
params.delete('ipv6')
params.delete('embeddedserver')

url.search = params.toString()

return url.toString()
}

async getShortUrl(opt_coordinate: number[] | undefined) {
const strippedUrl = this.getStrippedUrl(opt_coordinate)

const data = new URLSearchParams()
data.set('url', strippedUrl.replace('5173', '8080'))

const response = await fetch(
'https://migration.geoportail.lu/short/create',
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: data.toString(),
}
)

return await response.json()
}

getSnappedUrl() {
return this.snappedUrl
}
Expand Down

0 comments on commit b474156

Please sign in to comment.