osr proxy

This commit is contained in:
2026-04-04 11:29:19 -04:00
parent 9b8b9ec664
commit 27a2904bab
20 changed files with 688 additions and 354 deletions

View File

@@ -39,6 +39,7 @@ export default defineConfigWithVueTs(
files: ['**/*.ts', '**/*.vue'],
rules: {
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
'vue/no-v-text-v-html-on-component': 'off'
},
},
// https://github.com/vuejs/eslint-config-typescript

208
package-lock.json generated
View File

@@ -10,6 +10,8 @@
"hasInstallScript": true,
"dependencies": {
"@quasar/extras": "^1.16.4",
"@sentry/tracing": "^7.120.4",
"@sentry/vue": "^10.47.0",
"@vue-leaflet/vue-leaflet": "^0.10.1",
"axios": "^1.2.1",
"leaflet": "^1.9.4",
@@ -1961,6 +1963,212 @@
"win32"
]
},
"node_modules/@sentry-internal/browser-utils": {
"version": "10.47.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.47.0.tgz",
"integrity": "sha512-bVFRAeJWMBcBCvJKIFCMJ1/yQToL4vPGqfmlnDZeypcxkqUDKQ/Y3ziLHXoDL2sx0lagcgU2vH1QhCQ67Aujjw==",
"license": "MIT",
"dependencies": {
"@sentry/core": "10.47.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/browser-utils/node_modules/@sentry/core": {
"version": "10.47.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.47.0.tgz",
"integrity": "sha512-nsYRAx3EWezDut+Zl+UwwP07thh9uY7CfSAi2whTdcJl5hu1nSp2z8bba7Vq/MGbNLnazkd3A+GITBEML924JA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/feedback": {
"version": "10.47.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.47.0.tgz",
"integrity": "sha512-pdvMmi4dQpX5S/vAAzrhHPIw3T3HjUgDNgUiCBrlp7N9/6zGO2gNPhUnNekP+CjgI/z0rvf49RLqlDenpNrMOg==",
"license": "MIT",
"dependencies": {
"@sentry/core": "10.47.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/feedback/node_modules/@sentry/core": {
"version": "10.47.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.47.0.tgz",
"integrity": "sha512-nsYRAx3EWezDut+Zl+UwwP07thh9uY7CfSAi2whTdcJl5hu1nSp2z8bba7Vq/MGbNLnazkd3A+GITBEML924JA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/replay": {
"version": "10.47.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.47.0.tgz",
"integrity": "sha512-ScdovxP7hJxgMt70+7hFvwT02GIaIUAxdEM/YPsayZBeCoAukPW8WiwztJfoKtsfPyKJ5A6f0H3PIxTPcA9Row==",
"license": "MIT",
"dependencies": {
"@sentry-internal/browser-utils": "10.47.0",
"@sentry/core": "10.47.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/replay-canvas": {
"version": "10.47.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.47.0.tgz",
"integrity": "sha512-A5OY8friSe6g8WAK4L8IeOPiEd9D3Ps40DzRH5j2f6SUja0t90mKMvHRcRf8zq0d4BkdB+JM7tjOkwxpuv8heA==",
"license": "MIT",
"dependencies": {
"@sentry-internal/replay": "10.47.0",
"@sentry/core": "10.47.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/replay-canvas/node_modules/@sentry/core": {
"version": "10.47.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.47.0.tgz",
"integrity": "sha512-nsYRAx3EWezDut+Zl+UwwP07thh9uY7CfSAi2whTdcJl5hu1nSp2z8bba7Vq/MGbNLnazkd3A+GITBEML924JA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/replay/node_modules/@sentry/core": {
"version": "10.47.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.47.0.tgz",
"integrity": "sha512-nsYRAx3EWezDut+Zl+UwwP07thh9uY7CfSAi2whTdcJl5hu1nSp2z8bba7Vq/MGbNLnazkd3A+GITBEML924JA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/tracing": {
"version": "7.120.4",
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.120.4.tgz",
"integrity": "sha512-Fz5+4XCg3akeoFK+K7g+d7HqGMjmnLoY2eJlpONJmaeT9pXY7yfUyXKZMmMajdE2LxxKJgQ2YKvSCaGVamTjHw==",
"license": "MIT",
"dependencies": {
"@sentry/core": "7.120.4",
"@sentry/types": "7.120.4",
"@sentry/utils": "7.120.4"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/browser": {
"version": "10.47.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.47.0.tgz",
"integrity": "sha512-rC0agZdxKA5XWfL4VwPOr/rJMogXDqZgnVzr93YWpFn9DMZT/7LzxSJVPIJwRUjx3bFEby3PcTa3YaX7pxm1AA==",
"license": "MIT",
"dependencies": {
"@sentry-internal/browser-utils": "10.47.0",
"@sentry-internal/feedback": "10.47.0",
"@sentry-internal/replay": "10.47.0",
"@sentry-internal/replay-canvas": "10.47.0",
"@sentry/core": "10.47.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/browser/node_modules/@sentry/core": {
"version": "10.47.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.47.0.tgz",
"integrity": "sha512-nsYRAx3EWezDut+Zl+UwwP07thh9uY7CfSAi2whTdcJl5hu1nSp2z8bba7Vq/MGbNLnazkd3A+GITBEML924JA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/core": {
"version": "7.120.4",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.120.4.tgz",
"integrity": "sha512-TXu3Q5kKiq8db9OXGkWyXUbIxMMuttB5vJ031yolOl5T/B69JRyAoKuojLBjRv1XX583gS1rSSoX8YXX7ATFGA==",
"license": "MIT",
"dependencies": {
"@sentry/types": "7.120.4",
"@sentry/utils": "7.120.4"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/tracing": {
"version": "7.120.4",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.120.4.tgz",
"integrity": "sha512-cAtpLh23qW3hoqZJ6c36EvFki5NhFWUSK71ALHefqDXEocMlfDc9I+IGn3B/ola2D2TDEDamCy3x32vctKqOag==",
"license": "MIT",
"dependencies": {
"@sentry-internal/tracing": "7.120.4"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/types": {
"version": "7.120.4",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.120.4.tgz",
"integrity": "sha512-cUq2hSSe6/qrU6oZsEP4InMI5VVdD86aypE+ENrQ6eZEVLTCYm1w6XhW1NvIu3UuWh7gZec4a9J7AFpYxki88Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/utils": {
"version": "7.120.4",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.120.4.tgz",
"integrity": "sha512-zCKpyDIWKHwtervNK2ZlaK8mMV7gVUijAgFeJStH+CU/imcdquizV3pFLlSQYRswG+Lbyd6CT/LGRh3IbtkCFw==",
"license": "MIT",
"dependencies": {
"@sentry/types": "7.120.4"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/vue": {
"version": "10.47.0",
"resolved": "https://registry.npmjs.org/@sentry/vue/-/vue-10.47.0.tgz",
"integrity": "sha512-zIscc2e1d70yGK4qi4nqBy87TaQBKy0aXza+PtxI9qUWelF4bw0SJoVSglbISw+eOO4y0CBxZVRibLuj0Kv1pw==",
"license": "MIT",
"dependencies": {
"@sentry/browser": "10.47.0",
"@sentry/core": "10.47.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@tanstack/vue-router": "^1.64.0",
"pinia": "2.x || 3.x",
"vue": "2.x || 3.x"
},
"peerDependenciesMeta": {
"@tanstack/vue-router": {
"optional": true
},
"pinia": {
"optional": true
}
}
},
"node_modules/@sentry/vue/node_modules/@sentry/core": {
"version": "10.47.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.47.0.tgz",
"integrity": "sha512-nsYRAx3EWezDut+Zl+UwwP07thh9uY7CfSAi2whTdcJl5hu1nSp2z8bba7Vq/MGbNLnazkd3A+GITBEML924JA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",

View File

@@ -16,6 +16,8 @@
},
"dependencies": {
"@quasar/extras": "^1.16.4",
"@sentry/tracing": "^7.120.4",
"@sentry/vue": "^10.47.0",
"@vue-leaflet/vue-leaflet": "^0.10.1",
"axios": "^1.2.1",
"leaflet": "^1.9.4",

View File

@@ -124,7 +124,7 @@ export default defineConfig((/* ctx */) => {
'/api': {
target: 'http://localhost:49151',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
// rewrite: (path) => path.replace(/^\/api/, ''),
},
'/socket.io': {
target: 'http://localhost:49151', // Your backend WebSocket server
@@ -134,24 +134,15 @@ export default defineConfig((/* ctx */) => {
changeOrigin: true,
// rewrite: (path) => path.replace(/^\/socket.io/, ''),
},
'/osm': {
target: 'https://nominatim.openstreetmap.org',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/osm/, ''),
headers: {
Referer: 'https://nominatim.openstreetmap.org/',
'User-Agent': 'map-sim-location/0.0.1 (iam@williambr.uno)',
},
},
'/ors': {
// target: 'https://router.project-osrm.org',
// target: 'https://router.project-osrm.org',
target: 'http://localhost:8080',
changeOrigin: true,
// rewrite: (path) => path.replace(/^\/osrm/, ''),
// headers: {
// Referer: 'https://router.project-osrm.org/',
// 'User-Agent': 'map-sim-location/0.0.1 (iam@williambr.uno)',
// },
// rewrite: (path) => path.replace(/^\/osrm/, ''),
// headers: {
// Referer: 'https://router.project-osrm.org/',
// 'User-Agent': 'map-sim-location/0.0.1 (iam@williambr.uno)',
// },
},
},
},

View File

@@ -1,31 +0,0 @@
import { defineBoot } from '#q-app/wrappers';
import axios, { type AxiosInstance } from 'axios';
declare module 'vue' {
interface ComponentCustomProperties {
$axios: AxiosInstance;
$api: AxiosInstance;
}
}
// Be careful when using SSR for cross-request state pollution
// due to creating a Singleton instance here;
// If any client changes this (global) instance, it might be a
// good idea to move this instance creation inside of the
// "export default () => {}" function below (which runs individually
// for each client)
const api = axios.create({ baseURL: 'http://localhost:5000/api' });
export default defineBoot(({ app }) => {
// for use inside Vue files (Options API) through this.$axios and this.$api
app.config.globalProperties.$axios = axios;
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
// so you won't necessarily have to import axios in each vue file
app.config.globalProperties.$api = api;
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
// so you can easily perform requests against your app's API
});
export { api };

18
src/boot/sentry.ts Normal file
View File

@@ -0,0 +1,18 @@
import { defineBoot } from '#q-app/wrappers'
import * as Sentry from '@sentry/vue';
export default defineBoot(({ app }) => {
Sentry.init({
app,
dsn: "https://c117cc7eee3a88df9d289edc77d57c60@o4511152447553536.ingest.us.sentry.io/4511152493559808",
sendDefaultPii: true,
enableLogs: true,
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration()
],
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
});
})

View File

@@ -0,0 +1,102 @@
<template>
<div>
{{ formattedAddressLine1 }}
<q-inner-loading v-if="loading">
<q-spinner-dots color="primary" />
</q-inner-loading>
</div>
<div>
{{ formattedAddressLine2 }}
<q-inner-loading v-if="loading">
<q-spinner-dots color="primary" />
</q-inner-loading>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import type { NominatimResponse } from 'components/models';
const props = defineProps({
address: Object as () => NominatimResponse,
});
const loading = ref(false);
const formattedAddressLine1 = computed(() => {
if (!loading.value && props.address) {
const formAddress: string = props.address.house_number + ' ' + props.address.road;
return formAddress;
} else {
return '';
}
});
const formattedAddressLine2 = computed(() => {
if (!loading.value && props.address) {
const town: string = props.address.city
? props.address.city
: props.address.village
? props.address.village
: ' ';
const stateAbbr = stateAbbrevMap[props.address.state] ?? props.address.state ?? ' ';
const formAddress: string = town + ', ' + stateAbbr + ' ' + props.address.postcode;
return formAddress;
} else {
return '';
}
});
const stateAbbrevMap: Record<string, string> = {
Alabama: 'AL',
Alaska: 'AK',
Arizona: 'AZ',
Arkansas: 'AR',
California: 'CA',
Colorado: 'CO',
Connecticut: 'CT',
Delaware: 'DE',
Florida: 'FL',
Georgia: 'GA',
Hawaii: 'HI',
Idaho: 'ID',
Illinois: 'IL',
Indiana: 'IN',
Iowa: 'IA',
Kansas: 'KS',
Kentucky: 'KY',
Louisiana: 'LA',
Maine: 'ME',
Maryland: 'MD',
Massachusetts: 'MA',
Michigan: 'MI',
Minnesota: 'MN',
Mississippi: 'MS',
Missouri: 'MO',
Montana: 'MT',
Nebraska: 'NE',
Nevada: 'NV',
'New Hampshire': 'NH',
'New Jersey': 'NJ',
'New Mexico': 'NM',
'New York': 'NY',
'North Carolina': 'NC',
'North Dakota': 'ND',
Ohio: 'OH',
Oklahoma: 'OK',
Oregon: 'OR',
Pennsylvania: 'PA',
'Rhode Island': 'RI',
'South Carolina': 'SC',
'South Dakota': 'SD',
Tennessee: 'TN',
Texas: 'TX',
Utah: 'UT',
Vermont: 'VT',
Virginia: 'VA',
Washington: 'WA',
'West Virginia': 'WV',
Wisconsin: 'WI',
Wyoming: 'WY',
};
</script>

View File

@@ -245,7 +245,6 @@ export class OpenRouteServiceV2 extends L.Class {
/**
* Leaflet factory function (typed)
* Attach to L.Routing.openrouteserviceV2 so that it's callable as L.Routing.openrouteserviceV2
*/
export function openrouteserviceV2(
apiKeyOrOptions: string | ORSRequestOptions,
@@ -264,15 +263,5 @@ export function openrouteserviceV2(
);
}
type LeafletWithRouting = typeof L & {
Routing?: {
openrouteserviceV2?: typeof openrouteserviceV2;
[key: string]: unknown;
};
};
const leafletWithRouting = L as LeafletWithRouting;
if (!leafletWithRouting.Routing) {
leafletWithRouting.Routing = {};
}
leafletWithRouting.Routing.openrouteserviceV2 = openrouteserviceV2;
// Do not mutate the ESM namespace import (`import * as L`) because it is non-extensible
// in production bundles. Consumers should import and use `openrouteserviceV2` directly.

View File

@@ -108,18 +108,41 @@
:lat-lng="safeMarkerLatLng"
@click="handleMarkerClick"
>
<L-Popup :options="{ closeOnClick: true }">
<q-list dense seperator style="min-width: 150px" class="bg-grey-10">
<q-item clickable v-close-popup @click="handleAddLocation">
<q-item-section avatar><q-icon name="add_location" /></q-item-section>
<L-Popup
:options="{ closeOnClick: true, closeButton: false, className: 'marker-popup' }"
>
<q-list dense seperator style="min-width: 100px" class="bg-grey-10">
<q-item clickable v-ripple @click="handleAddLocation">
<q-item-section avatar>
<q-avatar
icon="add_location"
color="primary"
text-color="white"
size="sm"
/>
</q-item-section>
<q-item-section>Add Location</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="setStartRoute">
<q-item-section avatar><q-icon name="add_location" /></q-item-section>
<q-item clickable v-ripple @click="setStartRoute">
<q-item-section avatar>
<q-avatar
icon="add_location"
color="primary"
text-color="white"
size="sm"
/>
</q-item-section>
<q-item-section>Set Route Start</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="setEndRoute">
<q-item-section avatar><q-icon name="add_location" /></q-item-section>
<q-item clickable v-ripple @click="setEndRoute">
<q-item-section avatar>
<q-avatar
icon="add_location"
color="primary"
text-color="white"
size="sm"
/>
</q-item-section>
<q-item-section>Set Route End</q-item-section>
</q-item>
</q-list>
@@ -186,7 +209,7 @@ import LRoutingMachine from 'components/LRoutingMachine.vue';
import LocationMark from 'components/LocationMark.vue';
import SetLocationDialog from 'components/SetLocationDialog.vue';
import { customRouter } from 'functions/serviceURL';
import { reverseGeocodeRateLimited } from 'functions/reverseGeocode';
//import { reverseGeocodeRateLimited } from 'functions/reverseGeocode';
import { useRoutingEvents } from '../composables/useRoutingEvents';
import { useMarkerContextMenu } from '../composables/useMarkerContextMenu';
import type { IRouter } from 'leaflet-routing-machine';
@@ -195,8 +218,7 @@ import type { IRouter } from 'leaflet-routing-machine';
import type {
coords,
SearchControlProps,
NominatimAddress,
routeSegments,
// NominatimAddress,
} from 'components/models';
import type { LeafletMouseEvent, Map } from 'leaflet';
@@ -208,7 +230,7 @@ import { useLeafletStore } from 'stores/leaflet';
import { favorites } from 'constants/favorites';
const leafletStore = useLeafletStore();
const { zoom, center, markerLatLng, qLocDrawer, routeSet, routeSegments } =
const { zoom, center, markerLatLng, qLocDrawer, routeSet, routeDirections } =
storeToRefs(leafletStore);
const socketStore = useSocketioStore();
@@ -226,7 +248,6 @@ const $q = useQuasar();
const mapRef = ref();
const responseMessage = ref('');
const miniState = ref(true);
const loading = ref(false);
const safeCenter = computed<[number, number]>(() => {
const lat = center.value?.[0];
const lng = center.value?.[1];
@@ -320,25 +341,54 @@ function updateMarker(event: LeafletMouseEvent) {
center.value = [event.latlng.lat, event.latlng.lng];
}
function routeToQueue() {
console.log('routeToQueue');
if (routeSet.value.start && routeSet.value.end && routeSegments.value) {
console.log('routeToQueue: start: ', routeSet.value.start);
setLocation({ lat: routeSet.value.start.lat, lng: routeSet.value.start.lng }, 0);
routeSegments.value.forEach((segment: routeSegments) => {
console.log('routeToQueue: segment: ', segment);
setLocation(
{ lat: segment.toCoordinates.lat, lng: segment.toCoordinates.lng },
segment.timeSeconds,
);
});
console.log('routeToQueue: end: ', routeSet.value.end);
setLocation({ lat: routeSet.value.end.lat, lng: routeSet.value.end.lng }, 0);
function closeAllPopups() {
if (mapRef.value) {
// Access the Leaflet map instance directly
mapRef.value.leafletObject.closePopup();
}
}
const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
async function routeToQueue() {
if (routeSet.value.start && routeSet.value.end && routeDirections) {
if (routeDirections.value && routeDirections.value.length > 0) {
for (const direction of routeDirections.value) {
if (direction.coordinates) {
await setLocation(
{ lat: Number(direction.coordinates.lat), lng: Number(direction.coordinates.lng) },
direction.time,
);
}
await delay(1000);
}
}
}
}
/* function routeToQueue() {
console.log('routeToQueue');
if (routeSet.value.start && routeSet.value.end && routeSegments) {
console.log('routeToQueue: start: ', routeSet.value.start);
setLocation(
{ lat: Number(routeSet.value.start.lat), lng: Number(routeSet.value.start.lng) },
0,
);
if (routeSegments.value) {
routeSegments.value.forEach((segment: routeSegments) => {
console.log('routeToQueue: segment: ', segment);
setLocation(
{ lat: Number(segment.toCoordinates.lat), lng: Number(segment.toCoordinates.lng) },
segment.timeSeconds,
);
});
}
console.log('routeToQueue: end: ', routeSet.value.end);
setLocation({ lat: Number(routeSet.value.end.lat), lng: Number(routeSet.value.end.lng) }, 0);
}
}
*/
function clearRoute() {
routeSegments.value = [];
void leafletStore.clearRouteSegments;
routingOptions.waypoints = [];
routeSet.value.start = { lat: null, lng: null };
routeSet.value.end = { lat: null, lng: null };
@@ -362,21 +412,23 @@ const updateRoute = () => {
const { clickedLatLng, handleMarkerClick, setStartRoute, setEndRoute } = useMarkerContextMenu(
routeSet,
updateRoute,
closeAllPopups,
);
function handleAddLocation() {
if (clickedLatLng.value) {
const latlng = clickedLatLng.value;
closeAllPopups();
$q.notify(`add location...${latlng.toString()}`);
reverseGeocode(latlng.lat, latlng.lng)
.then((data) => {
const NomAddress = data.address as unknown as NominatimAddress;
// reverseGeocode(latlng.lat, latlng.lng)
// .then((data) => {
// const NomAddress = data.address as unknown as NominatimAddress;
$q.dialog({
component: SetLocationDialog,
componentProps: {
lat: Number(latlng.lat),
lng: Number(latlng.lng),
address: NomAddress,
// address: NomAddress,
},
})
.onOk((delay: number) => {
@@ -396,13 +448,13 @@ function handleAddLocation() {
.onDismiss(() => {
console.log('Dialog dismissed');
});
})
.catch((error) => {
console.error('Error fetching reverse geocode:', error);
});
// })
// .catch((error) => {
// console.error('Error fetching reverse geocode:', error);
// });
}
}
/*
async function reverseGeocode(lat: number, lng: number) {
loading.value = true;
try {
@@ -416,29 +468,36 @@ async function reverseGeocode(lat: number, lng: number) {
loading.value = false;
}
}
*/
function setLocation(coords: coords, delay: number) {
async function setLocation(coords: coords, delay: number) {
return new Promise((resolve, reject) => {
let notType: string = 'positive';
try {
const setCmdRsp = socketStore.simulationControl('add', delay, coords.lat, coords.lng);
if (setCmdRsp.sts === 'error') {
notType = 'negative';
}
if (setCmdRsp.msg) {
responseMessage.value = setCmdRsp.msg;
}
if (setCmdRsp.sts === 'error') {
notType = 'negative';
reject(new Error(setCmdRsp.msg || 'Unknown error'));
}
resolve(setCmdRsp);
} catch (error: unknown) {
notType = 'negative';
if (error instanceof Error) {
console.error('Error setting location:', error.message);
responseMessage.value = `Failed to set location: ${error.message}`;
reject(error);
} else {
console.error('Error setting location:', error);
responseMessage.value = `Failed to set location: Unknown error`;
reject(new Error('Unknown error'));
}
} finally {
$q.notify({ type: notType, message: responseMessage.value });
}
});
}
function zoomToCoods(arg: string) {
@@ -507,4 +566,9 @@ onMounted(() => {
.leafletDrawer
background-color: $dark
color: $dark
.marker-popup .leaflet-popup-content-wrapper, .marker-popup .leaflet-popup-tip
background-color: $dark
.marker-popup .leaflet-popup-content
margin: 13px 10px 13px 10px
</style>

View File

@@ -2,15 +2,18 @@
import { storeToRefs } from 'pinia';
import { useSocketioStore } from 'stores/socketio';
import { computed, onMounted, onUnmounted, ref } from 'vue';
import type { NominatimResponse } from 'components/models';
import { Icon, PinCirclePanel } from 'leaflet-extra-markers';
import FormattedAddress from 'components/FormattedAddress.vue';
const socketStore = useSocketioStore();
const { currentLocation, locationQueueOrder, simulationRunning } = storeToRefs(socketStore);
const props = defineProps({
address: {
type: String,
type: Object as () => NominatimResponse,
required: true,
},
latitude: {
@@ -52,7 +55,7 @@ const props = defineProps({
index: {
type: Number,
default: 0,
}
},
});
// Define custom events that this component can emit
@@ -133,7 +136,7 @@ const humanReadableDateTime = (iso: string) => {
minute: '2-digit',
});
};
/*
const stateAbbrevMap: Record<string, string> = {
Alabama: 'AL',
Alaska: 'AK',
@@ -204,6 +207,7 @@ function formatAddress(input: string): string {
return `${streetNumber} ${streetName}, ${city}, ${state} ${zip}`;
}
*/
const currentIndex = computed(() => {
return currentLocation.value ? locationQueueOrder.value.indexOf(currentLocation.value.loc_id) : 0;
@@ -260,8 +264,8 @@ onUnmounted(() => {
<template>
<q-item v-ripple clickable :active="active" @click="itemClicked" :class="itemClass">
<q-item-section>
<q-item-label
>{{ formatAddress(address) }}
<q-item-label>
<FormattedAddress :address="props.address" />
<q-tooltip> {{ latitude }}, {{ longitude }} </q-tooltip>
</q-item-label>
<q-item-label caption lines="1" v-if="start && simulationRunning">

View File

@@ -17,7 +17,7 @@ const $q = useQuasar();
const leafletStore = useLeafletStore();
const socketStore = useSocketioStore();
const { center, markerLatLng } = storeToRefs(leafletStore);
const { center, markerLatLng, zoom } = storeToRefs(leafletStore);
const { simulationRunning, simulationState, simulationQueueLength, icloudMonitor, testMode } =
storeToRefs(socketStore);
@@ -57,6 +57,7 @@ function hasSubitems(item: unknown): item is FavoriteWithSubitems {
function handleFavClick(coords: coords) {
center.value = [coords.lat, coords.lng];
markerLatLng.value = [coords.lat, coords.lng];
zoom.value = 15;
}
function handleTestToggle() {
@@ -181,7 +182,7 @@ function handleControlClick(cmdAttr: ControlAction) {
<template>
<q-toolbar :class="testMode ? 'bg-warning text-black' : 'bg-primary text-white'">
<q-btn
<!-- <q-btn
@click="
$emit('drawer');
leafletStore.toggleQLocDrawer();
@@ -192,6 +193,7 @@ function handleControlClick(cmdAttr: ControlAction) {
icon="menu"
class="q-mr-sm"
/>
-->
<q-separator dark inset />
<q-space />
<q-btn

View File

@@ -1,16 +1,16 @@
<template>
<q-dialog ref="dlgRef" persistent>
<q-card class="bg-secondary text-grey-1 add-loc-card">
<q-card-section>
<q-avatar icon="add_location" color="dark" text-color="white" />
<span class="text-h6"> Add Location to Queue</span>
</q-card-section>
<q-card class="bg-dark text-grey-1 add-loc-card q-pa-sm">
<q-toolbar>
<q-avatar icon="add_location" color="primary" text-color="white" />
<q-toolbar-title>Add Location to Queue</q-toolbar-title>
</q-toolbar>
<q-card-section class="q-ml-lg">
<div class="q-mb-sm">Are you sure you want to set location to:</div>
<div class="q-ml-lg">{{ formattedAddressLine1 }}</div>
<div class="q-ml-lg">{{ formattedAddressLine2 }}</div>
<div class="q-ml-lg">
<formatted-address :address="address" />
<q-input
class="q-mt-sm"
style="max-width: 150px"
v-model.number="delay"
filled
@@ -20,6 +20,7 @@
suffix="seconds"
color="grey-4"
/>
</div>
</q-card-section>
<q-separator />
<q-card-actions align="right">
@@ -32,42 +33,35 @@
<script setup lang="ts">
import { useDialogPluginComponent } from 'quasar';
import { computed, ref } from 'vue';
import type { PropType } from 'vue';
import type { NominatimAddress } from 'components/models';
import { reverseGeocodeRateLimited } from 'functions/reverseGeocode';
import { ref, onMounted } from 'vue';
import FormattedAddress from 'components/FormattedAddress.vue';
const props = defineProps({
lat: { type: Number, required: true },
lng: { type: Number, required: true },
address: { type: Object as PropType<NominatimAddress>, required: false },
});
const loading = ref(true);
const delay = ref(0);
defineEmits([...useDialogPluginComponent.emits]);
const { dialogRef: dlgRef, onDialogOK, onDialogCancel } = useDialogPluginComponent();
const formattedAddressLine1 = computed(() => {
if (props.address) {
const formAddress: string = props.address.house_number + ' ' + props.address.road;
return formAddress;
} else {
return '';
}
});
const address = ref();
const formattedAddressLine2 = computed(() => {
if (props.address) {
const town: string = props.address.city
? props.address.city
: props.address.village
? props.address.village
: ' ';
const formAddress: string = town + ', ' + props.address.state + ' ' + props.address.postcode;
return formAddress;
} else {
return '';
onMounted(async () => {
try {
loading.value = true;
const response = await reverseGeocodeRateLimited(props.lat, props.lng);
console.log('reverse geocode response: ', response);
address.value = response;
} catch (error) {
console.error('Error fetching reverse geocode:', error);
throw error;
} finally {
loading.value = false;
}
});

View File

@@ -2,12 +2,25 @@ export interface CtrlAttrs {
[key: string]: CtrlAttr;
}
export type SimulationCommands = 'start' | 'pause' | 'resume' | 'clear' | 'end' | 'add' | 'test-mode';
export type SimulationCommands =
| 'start'
| 'pause'
| 'resume'
| 'clear'
| 'end'
| 'add'
| 'test-mode';
export type DeviceCommands = 'start_tunnel' | 'stop_tunnel' | 'shutdown' | 'reboot';
export type TunnelCommands = 'start' | 'start-watcher' | 'end-watcher' | 'shutdown' | 'restart' | 'clear' | 'cancel' ;
export type TunnelCommands =
| 'start'
| 'start-watcher'
| 'end-watcher'
| 'shutdown'
| 'restart'
| 'clear'
| 'cancel';
export interface CtrlAttr {
name: string;
@@ -28,7 +41,7 @@ export interface DevCtrlAttr {
}
export interface LocationQueue {
[key: string]: LocationMark
[key: string]: LocationMark;
}
interface LocationMark {
@@ -37,8 +50,8 @@ interface LocationMark {
longitude: number | undefined | null;
address?: string | undefined | null;
delay?: number | undefined | null;
start?: string | undefined | null;
end?: string | undefined | null ;
start: string | undefined | null;
end?: string | undefined | null;
}
// SERVER TO CLIENT
@@ -240,10 +253,10 @@ export interface SearchControlProps {
export interface CurrentLocation {
loc_id: string;
latitude: number| null | undefined;
longitude: number| null | undefined;
// start_time?: string | null | undefined;
// end_time?: string | null | undefined
latitude: number | null | undefined;
longitude: number | null | undefined;
// start_time?: string | null | undefined;
// end_time?: string | null | undefined
next_move?: number | null | undefined;
}
@@ -254,31 +267,29 @@ export interface NextLocation {
time_at_location?: number | null;
}
export interface NominatimReverseResponse {
place_id: number
licence: string
osm_type: string
osm_id: number
lat: string
lon: string
class: string
type: string
place_rank: number
importance: number
addresstype: string
name: string
display_name: string
address: NominatimAddress
boundingbox: string[]
place_id: number;
licence: string;
osm_type: string;
osm_id: number;
lat: string;
lon: string;
class: string;
type: string;
place_rank: number;
importance: number;
addresstype: string;
name: string;
display_name: string;
address: NominatimAddress;
boundingbox: string[];
}
export interface NominatimAddress {
house_number: string;
road: string;
village?: string;
city? : string;
city?: string;
county: string;
state: string;
'ISO3166-2-lvl4': string;
@@ -289,8 +300,53 @@ export interface NominatimAddress {
export interface routeSegments {
fromWaypoint: number;
toWaypoint: number,
distanceMeters: number,
timeSeconds: number,
toCoordinates: {lat: number, lng: number};
toWaypoint: number;
distanceMeters: number;
timeSeconds: number;
toCoordinates: LatLng;
}
export interface RoutesSet {
[key: string]: RouteSet;
}
export interface LatLng {
lat: number | null | undefined;
lng: number | null | undefined;
}
export interface routeDirections {
dirIndex: number;
coordinateIndex: number;
text: string;
distance: number;
time: number;
coordinates: LatLng | null | undefined;
}
export interface RouteSet {
start: [number, number] | [null, null] | [undefined, undefined] | null | undefined;
end: [number, number] | [null, null] | [undefined, undefined] | null | undefined;
wayPoints?: [number, number][] | [null, null] | [undefined, undefined] | null | undefined;
}
// TypeScript Interface for Reverse Geocoding Response
export interface NominatimResponse {
house_number?: string | null | undefined;
road: string;
neighbourhood?: string | null | undefined;
suburb?: string | null | undefined;
county: string;
city?: string | null | undefined;
village?: string | null | undefined;
state: string;
'ISO3166-2-lvl4': string;
postcode: string;
country: string;
country_code: string;
}
export interface NominatimRequest {
latitude: number;
longitude: number;
}

View File

@@ -13,17 +13,23 @@ type RouteSetLike = {
[key: string]: unknown;
};
export function useMarkerContextMenu(routeSet: Ref<RouteSetLike>, updateRoute: () => void) {
export function useMarkerContextMenu(
routeSet: Ref<RouteSetLike>,
updateRoute: () => void,
closeAllPopups: () => void,
) {
const clickedLatLng = ref<LeafLet.LatLng | null>(null);
const handleMarkerClick = (event: LeafletMouseEvent) => {
console.log('marker clicked', event);
closeAllPopups();
clickedLatLng.value = event.latlng;
LeafLet.DomEvent.stopPropagation(event.originalEvent);
};
const setStartRoute = () => {
if (!clickedLatLng.value) return;
closeAllPopups();
(routeSet.value as RouteSet).start = clickedLatLng.value;
console.log('setStartRoute: ', routeSet.value.start);
};
@@ -31,6 +37,7 @@ export function useMarkerContextMenu(routeSet: Ref<RouteSetLike>, updateRoute: (
const setEndRoute = () => {
if (!clickedLatLng.value) return;
(routeSet.value as RouteSet).end = clickedLatLng.value;
closeAllPopups();
console.log('setEndRoute: ', routeSet.value.end);
updateRoute();
console.log('updating Route');

View File

@@ -4,7 +4,6 @@ import { storeToRefs } from 'pinia';
const leafletStore = useLeafletStore();
const { routeSegments, routeDirections } = storeToRefs(leafletStore);
type RouteSummary = {
totalDistance: number;
totalTime: number;
@@ -17,6 +16,11 @@ type RouteWaypoint = {
};
};
type RouteCoordinates = {
lat: number;
lng: number;
};
type RouteInstructions = {
text: string;
distance: number;
@@ -30,6 +34,7 @@ type RouteResult = {
inputWaypoints?: RouteWaypoint[];
instructions?: RouteInstructions[];
waypoints?: RouteWaypoint[];
coordinates?: RouteCoordinates[];
};
export function useRoutingEvents() {
@@ -50,24 +55,32 @@ export function useRoutingEvents() {
toWaypoint: index + 1,
distanceMeters: segment.totalDistance,
timeSeconds: segment.totalTime,
toCoordinates: route.inputWaypoints?.[index + 1]?.latLng ?? null,
toCoordinates: route.inputWaypoints?.[index + 1]?.latLng ??
route.waypoints?.[index + 1]?.latLng ?? { lat: 0, lng: 0 },
}));
if (routeSegments) {
routeSegments.value = segmentSummary;
}
console.log('Waypoint segment summary:', segmentSummary);
}
if (route.instructions?.length) {
const directionsSummary = route.instructions.map((direction, inx) => ({
waypoint: inx,
dirIndex: inx,
coordinateIndex: direction.index,
text: direction.text,
distance: direction.distance,
time: direction.time,
coordinates: route.waypoints?.[direction.index],
coordinates: {
lat: route.coordinates?.[direction.index]?.lat,
lng: route.coordinates?.[direction.index]?.lng,
},
}));
console.log('Direction waypoint segment summary:', directionsSummary);
if (routeDirections) {
routeDirections.value = directionsSummary;
}
}
};
return {

View File

@@ -1,26 +1,13 @@
// services/nominatimService.ts
import { osm } from 'boot/axios';
import { api } from 'boot/axios';
import type { NominatimResponse, NominatimRequest } from 'components/models';
// TypeScript Interface for Reverse Geocoding Response
export interface NominatimResponse {
place_id: number;
display_name: string;
address: {
road?: string;
city?: string;
country?: string;
[key: string]: unknown;
};
lat: string;
lon: string;
}
const API_URL = '/reverse';
// Simple debounce to respect 1s limit
let lastRequestTime = 0;
export const reverseGeocodeRateLimited = async (lat: number, lon: number): Promise<NominatimResponse> => {
export const reverseGeocodeRateLimited = async (
lat: number,
lon: number,
): Promise<NominatimResponse> => {
const now = Date.now();
const timeSinceLast = now - lastRequestTime;
@@ -28,19 +15,10 @@ export const reverseGeocodeRateLimited = async (lat: number, lon: number): Promi
if (timeSinceLast < 1000) {
await new Promise((resolve) => setTimeout(resolve, 1000 - timeSinceLast));
}
const response = await osm.get<NominatimResponse>(API_URL, {
params: {
lat,
lon,
format: 'jsonv2',
addressdetails: 1,
},
headers: {
// It is mandatory to set a descriptive User-Agent
'User-Agent': 'Vue3App/1.0 (your-email@example.com)',
},
});
const response = await api.post<NominatimResponse>('/rev_geocode', {
latitude: lat,
longitude: lon,
} as NominatimRequest);
lastRequestTime = Date.now();
return response.data;

View File

@@ -1,9 +1,10 @@
import { openrouteserviceV2 } from 'components/L.Routing.OpenRouteServiceV2';
export const customRouter = openrouteserviceV2({
api_key: '',
profile: 'driving-car',
geometry_simplify: true,
host: '/ors',
host: '/api/proxy/ors/',
});
//export const customRouter = L.Routing.osrmv1({

View File

@@ -1,37 +1,6 @@
import { defineStore, acceptHMRUpdate } from 'pinia';
import { favorites } from 'constants/favorites'
interface RoutesSet {
[key: string]: RouteSet
}
interface LatLng {
lat: number | null | undefined;
lng: number | null | undefined;
}
interface routeSegments {
fromWaypoint: number;
toWaypoint: number;
distanceMeters: number;
timeSeconds: number;
toCoordinates: LatLng | null | undefined;
}
interface routeDirections {
waypoint: number;
text: string;
distance: number;
time: number;
coordinates: LatLng | null | undefined;
}
interface RouteSet {
start: [number, number] | [null, null] | [undefined, undefined] | null | undefined;
end: [number, number] | [null, null] | [undefined, undefined] | null | undefined;
wayPoints?: [number, number][] | [null, null] | [undefined, undefined] | null | undefined;
}
import type { RoutesSet, LatLng, routeSegments, routeDirections } from 'components/models';
interface State {
zoom: number;
@@ -39,8 +8,8 @@ interface State {
markerLatLng: [number, number] | [null, null] | null;
qLocDrawer: boolean;
routeSet: {
start: LatLng | null | undefined;
end: LatLng | null | undefined;
start: LatLng;
end: LatLng;
wayPoints?: LatLng[] | null | undefined;
};
routesSet: RoutesSet[] | null;
@@ -60,12 +29,15 @@ export const useLeafletStore = defineStore('leaflet', {
end: { lat: null, lng: null },
wayPoints: null,
},
routesSet: null,
routeSegments: null,
routeDirections: null,
routesSet: [],
routeSegments: [],
routeDirections: [],
};
},
actions: {
clearRouteSegments() {
this.routeSegments = [];
},
setCenter(lat: number, lng: number) {
this.center = [lat, lng];
},

View File

@@ -14,7 +14,6 @@ import type {
SimulationStatus,
} from 'components/models';
const $q = useQuasar();
const debugLog: boolean = true;
@@ -115,14 +114,19 @@ export const useSocketioStore = defineStore('socketio', {
if (debugLog) {
console.log('event: simulation_status received: ', data);
}
console.log('updating currentLocation', data)
console.log('updating currentLocation', data);
this.currentLocation = {
loc_id: data.loc_id,
latitude: data.latitude,
longitude: data.longitude,
next_move: data.next_move,
};
this.locationQueueData[data.loc_id]['start'] = data.start;
if (data.start) {
const queueItem = this.locationQueueData[data.loc_id];
if (queueItem) {
queueItem.start = data.start;
}
}
});
socket.on('icloud_2fa_request', (callback) => {
@@ -226,7 +230,8 @@ export const useSocketioStore = defineStore('socketio', {
fnctRtn = { sts: 'OK', msg: 'iCloud Monitor: ' + response.status };
this.icloudMonitor = true;
}
});
},
);
break;
case 'stop':
if (debugLog) {
@@ -258,7 +263,8 @@ export const useSocketioStore = defineStore('socketio', {
fnctRtn = { sts: 'OK', msg: 'iCloud Monitor: ' + response.status };
this.icloudMonitor = false;
}
});
},
);
break;
case 'status':
if (debugLog) {
@@ -289,11 +295,14 @@ export const useSocketioStore = defineStore('socketio', {
'iCloud Monitor Enabled: ' +
response.icloud_monitor_enabled +
'iCloud Monitor Running: ' +
response.icloud_monitor_running
response.icloud_monitor_running,
};
}
this.icloudMonitor = !!(response.icloud_monitor_enabled && response.icloud_monitor_running);
});
this.icloudMonitor = !!(
response.icloud_monitor_enabled && response.icloud_monitor_running
);
},
);
break;
default:
fnctRtn = { sts: 'error', msg: 'Invalid command' };
@@ -430,9 +439,9 @@ export const useSocketioStore = defineStore('socketio', {
});
break;
case 'add':
// if (this.simulationState == 'ENDED' || !this.simulationRunning) {
// throw new Error('Simulation is not running');
// }
// if (this.simulationState == 'ENDED' || !this.simulationRunning) {
// throw new Error('Simulation is not running');
// }
if (!latitude || !longitude) {
throw new Error('latitude or longitude not set');
}

View File

@@ -1,46 +0,0 @@
import type { OpenStreetMapProvider } from 'leaflet-geosearch';
export interface DeviceShort {
udid: string | null;
connection_type: string | null;
}
export interface StatusUpdate {
device_connected: boolean;
device_count: number;
// devices?: Array<DeviceShort>;
udid?: string | null;
device_name?: string | null;
product_version?: string | null;
phone_number?: string | null;
developer_mode_enabled?: boolean;
ddi_mounted?: boolean;
rsd_address?: string | null;
rsd_port?: number;
lockdown_trusted_port?: number;
lockdown_untrusted_port?: number;
lockdown_trusted_reachable?: boolean;
lockdown_untrusted_reachable?: boolean;
dtservicehub_reachable?: boolean;
}
export interface coords {
lat: number;
lng: number;
}
export interface SearchControlProps {
provider: OpenStreetMapProvider;
showMarker: boolean;
autoClose: boolean;
updateMap: boolean;
showPopup: boolean;
style: 'button' | 'bar';
acceptAutoLoad: boolean;
autoComplete: boolean;
autoCompleteDelay: number;
retainZoomLevel: boolean;
animateZoom: boolean;
keepResult: boolean;
}