-
-
- No route to be displayed yet
-
-
-
-
- {{ sensorRouteList.length }} Bins
-
-
-
-
-
-
-
-
-
-
-
- {{ sensor.address_line1 }}
- {{ sensor.address_line2 }}
-
-
-
-
-
-
-
-
-
- {{ isRouteOptimized ? 'Optimized!' : 'Optimize route' }}
-
-
- Export route
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ Note: Currently only 25 bins can be added to the route. Please deselect {{ routeStore.getSelectedRouteList - 25 }} bin(s).
-
+
+ Find Route
+
+
+
+
@@ -159,33 +101,11 @@
}
.routes-list {
- margin-top: 40px;
width: 100%;
- &__route-container {
- border: 1px solid $lightergrey;
- border-radius: 20px;
- @include fontBodySmall;;
- }
-
- &__route {
- margin-bottom: 12px;
- &:last-child {
- margin-bottom: 0;
- }
- }
-
- &__export {
- display: flex;
- align-items: center;
- }
-
- &__items {
- cursor: pointer;
- }
-
- :deep .v-btn--size-default {
- min-width: 24px;
+ &__warning-text {
+ max-width: 200px;
+ @include fontBodySmall;
}
}
\ No newline at end of file
diff --git a/app_vue/src/components/sensorSidebar.vue b/app_vue/src/components/sensorSidebar.vue
index 276c9fcb..c5b751ac 100644
--- a/app_vue/src/components/sensorSidebar.vue
+++ b/app_vue/src/components/sensorSidebar.vue
@@ -88,11 +88,6 @@
}
}
- .routes-list {
- margin-top: 40px;
- width: 100%;
- }
-
.map-updated {
display: none; //TODO: switch to flex when needed
// display: flex;
diff --git a/app_vue/src/data/latestReadingsMock.json b/app_vue/src/data/latestReadingsMock.json
index c7f7f217..63a05e27 100644
--- a/app_vue/src/data/latestReadingsMock.json
+++ b/app_vue/src/data/latestReadingsMock.json
@@ -133,8 +133,8 @@
"id": "265434817",
"sensor_type": "solid bin level",
"fill_level": 40,
- "lat": 43.351197,
- "long": -79.347344,
+ "lat": 43.452197,
+ "long": -80.147244,
"manufacturer": "Sensational Sensors",
"bin_name": "Cherry Bin",
"address_line1": "99473 Fruit Blvd",
diff --git a/app_vue/src/stores/route_store.js b/app_vue/src/stores/route_store.js
index ed5de58a..9b260524 100644
--- a/app_vue/src/stores/route_store.js
+++ b/app_vue/src/stores/route_store.js
@@ -1,79 +1,127 @@
import { defineStore } from 'pinia'
+import { getOptimizedRoute } from '@/utils/optimizeRouteHelper';
export const useRouteStore = defineStore('route', {
state: () => ({
- sensorRouteList: [], // list of sensor objects part of the route
- enableOptimizeRoute: true, // enables optimize route button to be clicked
- isRouteOptimized: false
+ selectedRouteList: [], // list of sensor objects part of the route
+ isRouteOptimized: false, // when google api has been run on the route and we want to present it
+ startPointAddress: '6900 Airport Rd Mississauga ON',
+ endPointAddress: '6900 Airport Rd Mississauga ON',
+ routeDuration: '',
+ routeDistance: '',
+ hasMappedStartEnd: false
}),
getters: {
- getSensorRouteList({sensorRouteList}) {
- return sensorRouteList;
+ getSelectedRouteList({selectedRouteList}) {
+ return selectedRouteList;
},
- getSensorRouteLatLong({sensorRouteList}) {
- if (sensorRouteList.length > 0) {
- return sensorRouteList.map(sensor => [sensor.lat, sensor.long]);
+ getSelectedRouteLatLong({selectedRouteList}) {
+ if (selectedRouteList.length > 0) {
+ return selectedRouteList.map(sensor => [sensor.lat, sensor.long]);
}
return [];
},
- getEnableOptimizeRoute({enableOptimizeRoute}) {
- return enableOptimizeRoute;
- },
getIsRouteOptimized({isRouteOptimized}) {
return isRouteOptimized;
},
+ getStartPointAddress({startPointAddress}) {
+ return startPointAddress;
+ },
+ getEndPointAddress({endPointAddress}) {
+ return endPointAddress;
+ },
+ getRouteDuration({routeDuration}) {
+ return routeDuration;
+ },
+ getRouteDistance({routeDistance}) {
+ return routeDistance;
+ },
+ getHasMappedStartEnd({hasMappedStartEnd}) {
+ return hasMappedStartEnd;
+ },
},
actions: {
+ setStartPoint(value) {
+ this.startPointAddress = value;
+ },
+ setHasMappedStartEnd(value) {
+ this.hasMappedStartEnd = value;
+ },
+ setEndPoint(value) {
+ this.endPointAddress = value;
+ },
+ setRouteDuration(value) {
+ this.routeDuration = value;
+ },
+ setRouteDistance(value) {
+ this.routeDistance = value;
+ },
+ setSelectedRouteList(value) {
+ this.selectedRouteList = value;
+ },
+ updateRouteListWithSensors(sensors) {
+ this.selectedRouteList = [...sensors];
+ },
addSensorToRoute(sensor) {
- this.sensorRouteList.push(sensor);
- // reset
- this.enableOptimizeRoute = true;
- this.isRouteOptimized = false;
+ this.selectedRouteList.push(sensor);
},
removeSensorFromRoute(sensor) {
- const index = this.sensorRouteList.indexOf(sensor);
+ const index = this.selectedRouteList.indexOf(sensor);
if (index > -1) {
- this.sensorRouteList.splice(index, 1);
+ this.selectedRouteList.splice(index, 1);
}
- // reset
- this.enableOptimizeRoute = true;
- this.isRouteOptimized = false;
},
clearSensorRoute() {
- this.sensorRouteList = [];
+ this.selectedRouteList = [];
// reset
- this.enableOptimizeRoute = true;
this.isRouteOptimized = false;
},
- setEnableOptimizedRoute(value) {
- this.enableOptimizeRoute = value;
- },
setIsRouteOptimized(value) {
this.isRouteOptimized = value;
},
- // reorders intermediate points in our sensorRouteList according to a list of given indexes (routeOrder)
+ isAlreadyInRoute(sensor) {
+ if (this.getSelectedRouteList.length > 0) {
+ return !!this.getSelectedRouteList.find(bin => bin.id === sensor.id);
+ }
+ return false
+ },
+ // reorders intermediate points in our selectedRouteList according to a list of given indexes (routeOrder)
updateWithOptimizedRoute(routeOrder) {
// assuming routeOrder looks like [originLocationSensor, midLocationSensor1...midLocationSensor4, destinationLocaitonSensor]
// variable to hold our intermediate waypoints for update (no start/end point included)
- const intermediates = [...this.sensorRouteList];
- intermediates.shift(); // remove start point
- intermediates.pop(); // remove end point
+ const intermediates = [...this.selectedRouteList];
// refer to google route order and match it to our defined route
// copy result to temp
let temp = [];
- const arrLength = intermediates.length;
- for (let i = 0; i < arrLength; i++) {
- temp[routeOrder[i]] = intermediates[i];
+ for (let i = 0; i < routeOrder.length; i++) {
+ temp[i] = intermediates[routeOrder[i]];
}
// replace intermediate waypoint array with temp
- for (let j = 0; j < arrLength; j++) {
+ for (let j = 0; j < routeOrder.length; j++) {
intermediates[j] = temp[j];
}
// update saved routelist with new route order
- this.sensorRouteList.splice(1, intermediates.length, ...intermediates);
+ this.selectedRouteList = [...intermediates];
+ },
+ async googOptimizeRoute() {
+ const googResponse = await getOptimizedRoute(this.getSelectedRouteList, this.startPointAddress, this.endPointAddress);
+ if (googResponse && googResponse.routes && googResponse.routes[0]) {
+ const routeOrder = googResponse.routes[0].optimizedIntermediateWaypointIndex; // [0,3,4]
+ this.updateWithOptimizedRoute(routeOrder); // update current route
+ this.setIsRouteOptimized(true); // set flag is optimized to true
+
+ if (googResponse.routes[0].duration) {
+ this.setRouteDuration(googResponse.routes[0]?.duration);
+ }
+
+ if (googResponse.routes[0].distanceMeters) {
+ this.setRouteDistance(googResponse.routes[0]?.distanceMeters);
+ }
+
+ }
}
},
})
\ No newline at end of file
diff --git a/app_vue/src/utils/formattingHelper.js b/app_vue/src/utils/formattingHelper.js
new file mode 100644
index 00000000..3be8ec8d
--- /dev/null
+++ b/app_vue/src/utils/formattingHelper.js
@@ -0,0 +1,15 @@
+export const getMinutesString = (stringSeconds) => {
+ let duration = stringSeconds.replace('s','');
+
+ const hours = Math.floor(duration / 3600);
+ duration = duration % 3600;
+ const minutes = Math.floor(duration / 60);
+
+ const hoursString = hours ? `${hours} hrs ` : '';
+ const minutesString = minutes ? `${minutes} min ` : '';
+ return hoursString + minutesString;
+};
+
+export const getKmFromMeterString = (meters) => {
+ return (meters / 1000).toFixed(0);
+};
\ No newline at end of file
diff --git a/app_vue/src/utils/getLatLngFromAddressHelper.js b/app_vue/src/utils/getLatLngFromAddressHelper.js
new file mode 100644
index 00000000..0b20710f
--- /dev/null
+++ b/app_vue/src/utils/getLatLngFromAddressHelper.js
@@ -0,0 +1,20 @@
+import axios from 'axios';
+
+
+// uses openstreetmap geocoding (opensource)
+// https://nominatim.openstreetmap.org/ui/search.html
+export const getLatLng = async (address) => {
+ const url = `https://nominatim.openstreetmap.org/search?format=json&limit=3&q=${address}`;
+ try {
+ const response = await axios.get(url);
+ if (response && response.data && response.data[0]) {
+ return [response.data[0].lat, response.data[0].lon];
+ }
+ return [];
+ } catch(e) {
+ console.error('error generating lat lng', e);
+ return [];
+ }
+};
+
+export default getLatLng
\ No newline at end of file
diff --git a/app_vue/src/utils/mapMarkerHelper.js b/app_vue/src/utils/mapMarkerHelper.js
index 0d6907cd..0f16dc85 100644
--- a/app_vue/src/utils/mapMarkerHelper.js
+++ b/app_vue/src/utils/mapMarkerHelper.js
@@ -17,19 +17,20 @@ const getBinIconName = (sensor) => {
}
export const getMaterialTypeIconURL = (materialType) => {
+ const directory = 'src/assets/images/materialTypeIcons';
if (!materialType) {
return 'src/assets/images/generic-material.png';
}
const materialMap = {
- 'Cardboard': 'src/assets/images/cardboard.svg', //confirmed
- 'Plastic': 'src/assets/images/plastic.svg', // confirmed
- 'Compost': 'src/assets/images/compost.svg', // confirmed
- 'Diesel': 'src/assets/images/oil-drum.svg',
- 'Dyed Diesel': 'src/assets/images/oil-drum.svg',
- 'Kerosene': 'src/assets/images/oil-drum.svg',
- 'Water': 'src/assets/images/plastic.svg', // TODO: confirm water icon
- 'Gas Oil': 'src/assets/images/oil-drum.svg',
- 'Mixed waste': 'src/assets/images/trash.svg'
+ 'Cardboard': `${directory}/cardboard.svg`, //confirmed
+ 'Plastic': `${directory}/plastic.svg`, // confirmed
+ 'Compost': `${directory}/compost.svg`, // confirmed
+ 'Diesel': `${directory}/oil-drum.svg`,
+ 'Dyed Diesel': `${directory}/oil-drum.svg`,
+ 'Kerosene': `${directory}/oil-drum.svg`,
+ 'Water': `${directory}/plastic.svg`, // TODO: confirm water icon
+ 'Gas Oil': `${directory}/oil-drum.svg`,
+ 'Mixed waste': `${directory}/trash.svg`
};
return materialMap[materialType] || 'src/assets/images/generic-material.png';
}
@@ -44,25 +45,26 @@ export const getIconAndProgressColor = (sensor) => {
const RED = '#C92828';
const GREEN = 'green';
const ORANGE = '#F07F2D';
+ const iconDirectory = 'src/assets/images/binMarkerIcons';
switch (iconName) {
case 'error':
- iconUrl = 'src/assets/images/alert-bin.png';
+ iconUrl = `${iconDirectory}/alert-bin.png`;
linearProgressColor = RED;
break;
case 'full':
- iconUrl = 'src/assets/images/full-bin.png';
+ iconUrl = `${iconDirectory}/full-bin.png`;
linearProgressColor = RED;
break;
case 'half-full':
- iconUrl = 'src/assets/images/half-full-bin.png';
+ iconUrl = `${iconDirectory}/half-full-bin.png`;
linearProgressColor = ORANGE;
break;
case 'empty':
- iconUrl = 'src/assets/images/empty-bin.png';
+ iconUrl = `${iconDirectory}/empty-bin.png`;
linearProgressColor = GREEN;
break;
default: // default is empty bin
- iconUrl = 'src/assets/images/empty-bin.png';
+ iconUrl = `${iconDirectory}/empty-bin.png`;
linearProgressColor = GREEN;
}
return { iconUrl, linearProgressColor };
diff --git a/app_vue/src/utils/optimizeRouteHelper.js b/app_vue/src/utils/optimizeRouteHelper.js
index ee149e55..748e9cc3 100644
--- a/app_vue/src/utils/optimizeRouteHelper.js
+++ b/app_vue/src/utils/optimizeRouteHelper.js
@@ -1,14 +1,17 @@
import axios from 'axios';
-const getGoogPayload = (sensorRouteList) => {
- const waypointArr = [...sensorRouteList]; // ensures original variable isnt modified
- const origin = waypointArr.shift(); // grab first sensor in route
- const destination = waypointArr.pop(); // grab last sensor in route
-
+const getGoogPayload = (selectedRouteList, originAddress, destinationAddress) => {
+ const origin = {
+ "address": originAddress
+ };
+ const destination = {
+ "address": destinationAddress
+ };
+ const waypointArr = [...selectedRouteList]; // ensures original variable isnt modified
const intermediates = waypointArr.map(waypoint => {
return {
- "location":{
- "latLng":{
+ "location": {
+ "latLng": {
"latitude": waypoint.lat,
"longitude": waypoint.long
}
@@ -16,24 +19,10 @@ const getGoogPayload = (sensorRouteList) => {
}
});
- const data = {
- "origin": { // specify starting point
- "location":{
- "latLng":{
- "latitude": origin.lat,
- "longitude": origin.long
- }
- }
- },
+ return {
+ origin,
intermediates,
- "destination":{ // specify ending point
- "location":{
- "latLng":{
- "latitude": destination.lat,
- "longitude": destination.long
- }
- }
- },
+ destination,
"travelMode": "DRIVE",
"routingPreference": "TRAFFIC_UNAWARE",
"computeAlternativeRoutes": false,
@@ -44,24 +33,22 @@ const getGoogPayload = (sensorRouteList) => {
},
"languageCode": "en-US",
"units": "IMPERIAL",
- "optimizeWaypointOrder": "true", // note this special feature being true charges us advanced:route pricing
+ "optimizeWaypointOrder": "true"
};
- return data;
};
// google api call to optimize route
// https://developers.google.com/maps/documentation/routes/opt-way
-export const getOptimizedRoute = async (sensorRouteList) => {
+export const getOptimizedRoute = async (selectedRouteList, originAddress, destinationAddress) => {
- if (sensorRouteList && sensorRouteList.length === 0) {
+ if (selectedRouteList && selectedRouteList.length === 0) {
return;
}
// google headers
- const googApiKey = 'AIzaSyAgixnED4py56GFy-b2hlfYgofEyISUjSo'; //TODO: move to .env
+ const googApiKey = 'AIzaSyAgixnED4py56GFy-b2hlfYgofEyISUjSo';
const googFieldMask = 'routes.duration,routes.distanceMeters,routes.optimizedIntermediateWaypointIndex';
const URL = 'https://routes.googleapis.com/directions/v2:computeRoutes';
- const data = getGoogPayload(sensorRouteList);
-
+ const data = getGoogPayload(selectedRouteList, originAddress, destinationAddress);
const config = {
headers: {
'Content-Type': 'application/json',
@@ -71,6 +58,7 @@ export const getOptimizedRoute = async (sensorRouteList) => {
};
try {
+ // if there is an empty object returned, then there is an incorrect lat/long passed
const response = await axios.post(URL, data, config);
if (response) {
return response.data;
diff --git a/sensational_sensors/main.py b/sensational_sensors/main.py
index 5a2c11e3..4e0549b9 100644
--- a/sensational_sensors/main.py
+++ b/sensational_sensors/main.py
@@ -151,8 +151,8 @@ class Sensor(BaseModel):
"sensor_type": SensorType.LIQUID_BIN_LEVEL,
"fill_level": 40,
"sim": "81kai41j8",
- "lat": 43.351197,
- "long": -79.347344,
+ "lat": 43.452197,
+ "long": -80.147244,
"man": "Sensational Sensors",
"bin_name": "Cherry Bin",
"address_line1": "99473 Fruit Blvd",