route work

This commit is contained in:
2026-06-12 07:22:59 -04:00
parent 1b19ee9de9
commit fee7bba0ce
7 changed files with 220 additions and 114 deletions
+23 -2
View File
@@ -37,12 +37,16 @@ type CommandAck = {
message: string; message: string;
}; };
function setOpenedAppFab(fab: ControlCategoryKey) { function setOpenedAppFab(fab: ControlCategoryKey | null) {
leafletStore.fabs.appControl.openedFab = fab; leafletStore.fabs.appControl.openedFab = fab;
} }
function handleOpenedAppFabUpdate(val: boolean, key: ControlCategoryKey) { function handleOpenedAppFabUpdate(val: boolean, key: ControlCategoryKey) {
if (val) setOpenedAppFab(key); if (val) {
setOpenedAppFab(key);
} else {
setOpenedAppFab(null);
}
} }
function formatUnknownError(commandLabel: string, error: unknown): NotificationResult { function formatUnknownError(commandLabel: string, error: unknown): NotificationResult {
@@ -138,6 +142,23 @@ async function runRoutingCommand(cmdAttr: ControlAction): Promise<NotificationRe
}; };
} }
if (cmdAttr.cmd === 'set_route_start_to_current_location') {
if (simulationStore.currentLocation.loc_id) {
const currentLocationData = simulationStore.getLocationData(simulationStore.currentLocation.loc_id);
const result = leafletStore.setRouteStart(currentLocationData.latitude, currentLocationData.longitude);
return {
type: result.type,
message: result.message || `${cmdAttr.name} requested`,
};
} else {
const geoLocat = await leafletStore.getGeoLocationAsync()
const result = leafletStore.setRouteStart(geoLocat.lat, geoLocat.lng);
return {
type: result.type,
message: result.message || `${cmdAttr.name} requested`,
};
}
}
return { return {
type: 'negative', type: 'negative',
message: `Unsupported routing command: ${cmdAttr.cmd}`, message: `Unsupported routing command: ${cmdAttr.cmd}`,
+16 -59
View File
@@ -1,27 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue'; import { computed, onMounted } from 'vue';
import { useLeafletStore } from 'stores/leaflet'; import { useLeafletStore } from 'stores/leaflet';
import { LocateControl } from 'leaflet.locatecontrol'; import { storeToRefs } from 'pinia';
const leafletStore = useLeafletStore(); const leafletStore = useLeafletStore();
const { locateControl } = storeToRefs(leafletStore);
const locateControl = new LocateControl({
position: 'topleft',
flyTo: true,
keepCurrentZoomLevel: true,
returnToPrevBounds: true,
layer: leafletStore.layerGroups.locateControl ?? undefined,
});
const locationControlInit = ref(false);
const locateControlActive = ref(false);
const locateControlFollowing = ref(false);
const locationFound = ref(false);
const iconColor = computed(() => const iconColor = computed(() =>
locateControlActive.value locateControl.value.status.active
? locationFound.value ? locationControl.value.status.Found
? locateControlFollowing.value ? locateControl.value.status.Following
? '#FC8428FF' ? '#FC8428FF'
: '#2074B6FF' : '#2074B6FF'
: '#FFFFFF' : '#FFFFFF'
@@ -29,7 +17,11 @@ const iconColor = computed(() =>
); );
const iconClass = computed(() => { const iconClass = computed(() => {
if (locateControlActive.value && !locationFound.value && !locateControlFollowing.value) { if (
locateControl.value.status.active &&
!locateControl.value.status.found &&
!locateControl.value.status.following
) {
return 'locate-control-icon-spinner'; return 'locate-control-icon-spinner';
} else { } else {
return ''; return '';
@@ -38,53 +30,18 @@ const iconClass = computed(() => {
const handleClick = () => { const handleClick = () => {
initialize(); initialize();
if (locateControlActive.value) { if (locateControl.value.status.active) {
console.log('Stopping locate control'); console.log('Stopping locate control');
locateControl.stop(); leafletStore.stopLocate();
} else { } else {
console.log('Starting locate control'); console.log('Starting locate control');
locateControl.start(); leafletStore.startLocate();
} }
}; };
function initialize() { function initialize() {
if (!locationControlInit.value) { if (!locationControl.value.status.init) {
if (leafletStore.mapObject) { leafletStore.initLocateControl();
locationControlInit.value = true;
leafletStore.mapObject.addControl(locateControl);
leafletStore.mapObject?.on('locateactivate', function () {
console.log('Locate control activated');
locateControlActive.value = true;
});
leafletStore.mapObject?.on('locatedeactivate', function () {
console.log('Locate control deactivated');
locateControlActive.value = false;
leafletStore.layerGroups.locateControl?.clearLayers();
});
leafletStore.mapObject?.on('locationfound', function (e) {
console.log('Location Found: ', e);
console.log('Location found:', e.latlng);
console.log('Accuracy:', e.accuracy, 'meters');
locateControlFollowing.value = true;
locationFound.value = true;
});
leafletStore.mapObject?.on('moveend', function () {
if (locateControlFollowing.value) {
locateControlFollowing.value = false;
}
});
leafletStore.mapObject?.on('locationtimeout', function (e) {
const timeoutEvent = e as typeof e & { count?: number };
console.log('Location timeout count:', timeoutEvent.count);
locationFound.value = false;
leafletStore.layerGroups.locateControl?.clearLayers();
});
}
} }
} }
@@ -269,6 +269,7 @@ function zoomToCoords(arg: string) {
" "
@item-clicked="zoomToCoords" @item-clicked="zoomToCoords"
@contextmenu.prevent="onDrawerContextMenu($event, key)" @contextmenu.prevent="onDrawerContextMenu($event, key)"
@touchstart.prevent="onDrawerContextMenu($event, key)"
active-class="text-orange" active-class="text-orange"
/> />
</VueDraggable> </VueDraggable>
+1
View File
@@ -52,6 +52,7 @@ export async function useAddLocation(
const simulationStore = useSimulationStore(); const simulationStore = useSimulationStore();
const locationStore = useLocationStore(); const locationStore = useLocationStore();
try { try {
console.log ('address: %s, getRevGeocode: %s', address, getRevGeocode);
const revGeo = const revGeo =
!address && getRevGeocode !address && getRevGeocode
/* ? await reverseGeocodeRateLimited(coords.lat, coords.lng) */ /* ? await reverseGeocodeRateLimited(coords.lat, coords.lng) */
+12 -1
View File
@@ -306,6 +306,16 @@ const baseControls = {
iconColor: 'grey-10', iconColor: 'grey-10',
isVisible: true, isVisible: true,
}, },
set_route_start_to_current_location: {
name: 'Set route start to current location',
cmd: 'set_route_start_to_current_location',
cmdClass: 'routing_cntrl_class',
icon: 'mdi-navigation',
cnfrm: false,
disabled: false,
iconColor: 'grey-10',
isVisible: true,
},
}, },
}, },
} satisfies Record<ControlCategoryKey, ControlCategoryDefinition>; } satisfies Record<ControlCategoryKey, ControlCategoryDefinition>;
@@ -349,7 +359,8 @@ export function useControls() {
return true; return true;
}), }),
routing: computed(() => { routing: computed(() => {
return Boolean(leafletStore.routeSet.start.lng && leafletStore.routeSet.end.lng); return true;
// return Boolean(leafletStore.routeSet.start.lng && leafletStore.routeSet.end.lng);
}), }),
} satisfies Record<ControlCategoryKey, ComputedRef<boolean>>; } satisfies Record<ControlCategoryKey, ComputedRef<boolean>>;
+18 -2
View File
@@ -23,7 +23,13 @@ useMeta({
function layoutClickHandler(event: LeafletMouseEvent) { function layoutClickHandler(event: LeafletMouseEvent) {
// if (!leafletStore.locationDrawerSettings.opened && !leafletStore.controlDrawerSettings.opened && !leafletStore.statusBarSettings.opened) { // if (!leafletStore.locationDrawerSettings.opened && !leafletStore.controlDrawerSettings.opened && !leafletStore.statusBarSettings.opened) {
if (!leafletStore.locationDrawerSettings.opened && !leafletStore.fabs.statusBar) { if (
!leafletStore.locationDrawerSettings.opened &&
!leafletStore.fabs.statusBar.opened &&
!leafletStore.fabs.layerControl.opened &&
!leafletStore.fabs.favorites.opened &&
!leafletStore.fabs.appControl.opened
) {
return; return;
} }
if ( if (
@@ -33,9 +39,19 @@ function layoutClickHandler(event: LeafletMouseEvent) {
) { ) {
leafletStore.locationsOpenedHandler(false); leafletStore.locationsOpenedHandler(false);
} }
if (!event.target.closest('#status-bar') && leafletStore.fabs.statusBar) { if (!event.target.closest('#status-bar') && leafletStore.fabs.statusBar.opened) {
leafletStore.fabs.statusBar.opened = false; leafletStore.fabs.statusBar.opened = false;
} }
if (!event.target.closest('#app-ctrl') && leafletStore.fabs.appControl.opened) {
leafletStore.fabs.appControl.opened = false;
}
if (!event.target.closest('#favorite-ctrl') && leafletStore.fabs.favorites.opened) {
leafletStore.fabs.favorites.opened = false;
}
if (!event.target.closest('#layer-ctrl') && leafletStore.fabs.layerControl.opened) {
leafletStore.fabs.layerControl.opened = false;
}
/* /*
if (!event.target.closest('#control-drawer') && !leafletStore.controlDrawerSettings.pinned) { if (!event.target.closest('#control-drawer') && !leafletStore.controlDrawerSettings.pinned) {
leafletStore.locationsOpenedHandler(false); leafletStore.locationsOpenedHandler(false);
+149 -50
View File
@@ -6,6 +6,7 @@ import * as L from 'leaflet';
import type { Map, LatLngExpression, LayerGroup, FeatureGroup, Marker } from 'leaflet'; import type { Map, LatLngExpression, LayerGroup, FeatureGroup, Marker } from 'leaflet';
import type { SearchResult } from 'components/models'; import type { SearchResult } from 'components/models';
import type { IRouter } from 'leaflet-routing-machine'; import type { IRouter } from 'leaflet-routing-machine';
import { LocateControl } from 'leaflet.locatecontrol';
import { customRouter } from 'functions/serviceURL'; import { customRouter } from 'functions/serviceURL';
import { startIcon, intermediateIcon, endIcon } from 'constants/icons'; import { startIcon, intermediateIcon, endIcon } from 'constants/icons';
import type { import type {
@@ -66,7 +67,20 @@ interface RoutingOptions {
createMarker: (i: number, waypoint: RoutingWaypoint, n: number) => L.Marker | false; createMarker: (i: number, waypoint: RoutingWaypoint, n: number) => L.Marker | false;
} }
interface State { interface LcStatus {
active: boolean;
found: boolean;
following: boolean;
init: boolean;
}
interface LocateControlInstance {
lc: ReturnType<typeof shallowRef<LocateControl | null>>;
status: LcStatus;
latLng: L.LatLng | null;
}
interface StoreState {
zoom: number; zoom: number;
center: [number, number] | [null, null] | null; center: [number, number] | [null, null] | null;
mapObject: ReturnType<typeof shallowRef<Map | null>>; mapObject: ReturnType<typeof shallowRef<Map | null>>;
@@ -85,6 +99,7 @@ interface State {
}; };
layerControl: LayerControl; layerControl: LayerControl;
layerGroups: LayerGroups; layerGroups: LayerGroups;
locateControl: LocateControlInstance;
routeSet: { routeSet: {
start: LatLng; start: LatLng;
end: LatLng; end: LatLng;
@@ -105,7 +120,7 @@ interface State {
} }
export const useLeafletStore = defineStore('leafletStore', { export const useLeafletStore = defineStore('leafletStore', {
state: (): State => { state: (): StoreState => {
return { return {
zoom: 10, zoom: 10,
center: [favorites.home.coords.lat, favorites.home.coords.lng], center: [favorites.home.coords.lat, favorites.home.coords.lng],
@@ -128,6 +143,16 @@ export const useLeafletStore = defineStore('leafletStore', {
findMy: shallowRef(null), findMy: shallowRef(null),
locateControl: shallowRef(null), locateControl: shallowRef(null),
}, },
locateControl: {
lc: shallowRef(null),
status: {
active: false,
following: false,
init: false,
found: false,
},
latLng: null as L.LatLng | null,
},
leafletDrawerOpen: false, leafletDrawerOpen: false,
locationDrawerSettings: { locationDrawerSettings: {
opened: false, opened: false,
@@ -171,45 +196,48 @@ export const useLeafletStore = defineStore('leafletStore', {
value: 1, value: 1,
label: 'Open Street Map', label: 'Open Street Map',
layer: shallowRef( layer: shallowRef(
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
{ maxZoom: 19,
maxZoom: 19, attribution: '© OpenStreetMap',
attribution: '© OpenStreetMap', minZoom: 2,
minZoom: 2, noWrap: true,
noWrap: true, }),
} ),
))
}, },
{
value: 2,
label: 'Topography',
layer: shallowRef(L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
minZoom: 2,
maxZoom: 17,
attribution: '© OpenTopoMap',
noWrap: true,
}))
},
{
value: 3,
label: 'Satellite',
layer: shallowRef(L.tileLayer(
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
{ {
minZoom: 2, value: 2,
maxZoom: 19, label: 'Topography',
attribution: '© ArcGIS', layer: shallowRef(
noWrap: true, L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
})) minZoom: 2,
} maxZoom: 17,
], attribution: '© OpenTopoMap',
noWrap: true,
}),
),
},
{
value: 3,
label: 'Satellite',
layer: shallowRef(
L.tileLayer(
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
{
minZoom: 2,
maxZoom: 19,
attribution: '© ArcGIS',
noWrap: true,
},
),
),
},
],
}, },
baseLayerWMS: { baseLayerWMS: {
selected: false, selected: false,
label: 'OpenStreetMap Transparent', label: 'OpenStreetMap Transparent',
layer: shallowRef(L.tileLayer.wms( layer: shallowRef(
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', L.tileLayer.wms('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
{
minZoom: 2, minZoom: 2,
maxZoom: 19, maxZoom: 19,
attribution: '© OpenStreetMap', attribution: '© OpenStreetMap',
@@ -217,8 +245,8 @@ export const useLeafletStore = defineStore('leafletStore', {
transparent: true, transparent: true,
format: 'image/png', format: 'image/png',
opacity: 0.5, opacity: 0.5,
} }),
)), ),
}, },
routingOptions: { routingOptions: {
waypoints: [], waypoints: [],
@@ -259,6 +287,61 @@ export const useLeafletStore = defineStore('leafletStore', {
setMap(map: Map) { setMap(map: Map) {
this.mapObject = map; this.mapObject = map;
}, },
initLocateControl() {
const lc = new LocateControl({
position: 'topleft',
flyTo: true,
keepCurrentZoomLevel: true,
returnToPrevBounds: true,
layer: this.layerGroups.locateControl ?? undefined,
});
this.locateControl.lc = lc;
this.locateControl.status.init = true;
if (this.mapObject) {
this.mapObject.addControl(lc);
this.mapObject.on('locationerror', () => {
this.locateControl.latLng = null;
});
this.mapObject.on('locateactivate', () => {
console.log('Locate control activated');
this.locateControl.status.active = true;
});
this.mapObject.on('locatedeactivate', () => {
console.log('Locate control deactivated');
this.locateControl.status.active = false;
this.layerGroups.locateControl?.clearLayers();
});
this.mapObject.on('locationfound', (e) => {
console.log('Location Found: ', e);
console.log('Location found:', e.latlng);
console.log('Accuracy:', e.accuracy, 'meters');
this.locateControl.latLng = e.latlng;
this.locateControl.status.following = true;
this.locateControl.status.found = true;
});
this.mapObject.on('moveend', () => {
if (this.locateControl.status.following) {
this.locateControl.status.following = false;
}
});
this.mapObject.on('locationtimeout', (e) => {
const timeoutEvent = e as typeof e & { count?: number };
console.log('Location timeout count:', timeoutEvent.count);
this.locateControl.status.found = false;
this.layerGroups.locateControl?.clearLayers();
});
}
},
startLocate() {
if (this.locateControl.lc) {
this.locateControl.lc.start();
}
},
stopLocate() {
if (this.locateControl.lc) {
this.locateControl.lc.stop();
}
},
/* /*
setLayerGroup(layerGroup: LayerGroup, name: string) setLayerGroup(layerGroup: LayerGroup, name: string)
this.layerGroups[name] = layerGroup; this.layerGroups[name] = layerGroup;
@@ -293,23 +376,23 @@ export const useLeafletStore = defineStore('leafletStore', {
this.zoom = zoom; this.zoom = zoom;
}, },
zoomIn() { zoomIn() {
this.mapObject?.zoomIn() this.mapObject?.zoomIn();
}, },
zoomOut() { zoomOut() {
this.mapObject?.zoomOut() this.mapObject?.zoomOut();
}, },
setBaseLayer(newVal?: number, oldVal?: number) { setBaseLayer(newVal?: number, oldVal?: number) {
if (newVal === undefined && this.baseLayer.selected === undefined) return; if (newVal === undefined && this.baseLayer.selected === undefined) return;
if (oldVal && this.mapObject) { if (oldVal && this.mapObject) {
const oldLayer = this.baseLayer.options.filter(u => u.value === oldVal)[0]; const oldLayer = this.baseLayer.options.filter((u) => u.value === oldVal)[0];
if (oldLayer) { if (oldLayer) {
console.log("OldLayer name: ", oldLayer.label); console.log('OldLayer name: ', oldLayer.label);
oldLayer.layer!.remove(); oldLayer.layer!.remove();
} }
} }
const bLayer = this.baseLayer.options.filter(u => u.value === this.baseLayer.selected)[0]; const bLayer = this.baseLayer.options.filter((u) => u.value === this.baseLayer.selected)[0];
if(this.mapObject && bLayer) { if (this.mapObject && bLayer) {
console.log("NewLayer name: ", bLayer.label); console.log('NewLayer name: ', bLayer.label);
bLayer.layer!.addTo(this.mapObject); bLayer.layer!.addTo(this.mapObject);
} }
}, },
@@ -422,12 +505,28 @@ export const useLeafletStore = defineStore('leafletStore', {
} }
return { type: result.notificationType, message: result.responseMessage }; return { type: result.notificationType, message: result.responseMessage };
}, },
setRouteStart(lat: number, lng: number) {
this.layerControl.routing = true;
this.routeSet.start = { lat: lat, lng: lng };
return { type: 'positive', message: 'Route start set.' };
},
getGeoLocationAsync() {
return new Promise((resolve, reject) => {
this.locateControl?.lc?.start();
this.mapObject?.once('locationfound', function (e) {
resolve(e.latlng);
});
this.mapObject?.once('locationerror', function (e) {
reject(new Error(e.message));
});
});
},
clearRoute() { clearRoute() {
this.layerControl.routing = false; this.layerControl.routing = false;
this.clearRouteSegments(); this.clearRouteSegments();
this.routeSet.start = {lat: null, lng: null}; this.routeSet.start = { lat: null, lng: null };
this.routeSet.end = {lat: null, lng: null}; this.routeSet.end = { lat: null, lng: null };
return {type: 'positive', message: 'Route cleared successfully'}; return { type: 'positive', message: 'Route cleared successfully' };
}, },
moveToLocation(location: SearchResult) { moveToLocation(location: SearchResult) {
if (!location) return; if (!location) return;
@@ -443,9 +542,9 @@ export const useLeafletStore = defineStore('leafletStore', {
// Otherwise, pan to center // Otherwise, pan to center
this.mapObject?.setView([lat, lng], 14); this.mapObject?.setView([lat, lng], 14);
} }
let searchMarker: Marker = new L.Marker([0,0]); let searchMarker: Marker = new L.Marker([0, 0]);
// Update/create a marker at the selected location // Update/create a marker at the selected location
if (searchMarker) { if (searchMarker) {
searchMarker.setLatLng([lat, lng]); searchMarker.setLatLng([lat, lng]);
} else { } else {
searchMarker = L.marker([lat, lng]).addTo(this.mapObject!); searchMarker = L.marker([lat, lng]).addTo(this.mapObject!);