diff --git a/src/assets/base.css b/src/assets/base.css index abc7ec8..1436eb4 100644 --- a/src/assets/base.css +++ b/src/assets/base.css @@ -1,14 +1,45 @@ @import url("https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"); + @media (width >= 1024px) { - body { - display: flex; - place-items: center; - } - - #app { - display: flex; - height: 100vh; - padding: 0; - flex: 1; - } + body { + display: flex; + place-items: center; + } + + #app { + display: flex; + height: 100vh; + padding: 0; + flex: 1; + } + + + .metrics-container { + display: flex; + gap: 16px; + flex-direction: row; + align-items: center; + justify-content: center; + } + + .metrics-container .metric { + display: flex; + font-size: 16px; + font-weight: 500; + flex-direction: column; + justify-content: center; + align-items: center; + } + .popup-title{ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding-bottom: 8px; + } + + .popup-title .key-value { + font-weight: bold; + font-size: 16px; + } } \ No newline at end of file diff --git a/src/components/MapView.vue b/src/components/MapView.vue index 485fdd7..f0394d5 100644 --- a/src/components/MapView.vue +++ b/src/components/MapView.vue @@ -3,12 +3,18 @@ import mapboxgl from "mapbox-gl"; import "mapbox-gl/dist/mapbox-gl.css"; import { onMounted, ref } from "vue"; import MapAPI from "@/components/api/MapAPI"; -import { get_datetime_format } from "@/utils/gps_utils"; import { ElNotification } from "element-plus"; import UpdateStatusComponent from "@/components/map/UpdateStatusComponent.vue"; import SpeedInfoComponent from "@/components/map/SpeedInfoComponent.vue"; import { Cron } from "croner"; +interface AlertData { + coords: number[]; + key_value: string; + useful: number; + useless: number; +} + const COLOR_DATA = [ { color: "#FF0000", info: "< 5 km/h" }, { color: "#FF4000", info: "5 - 10 km/h" }, @@ -24,7 +30,6 @@ const COLOR_DATA = [ const TEMPORAL_RANGE = 15; const style = ref("mapbox://styles/mapbox/dark-v10"); -const elSwitch = ref(false); const mapContainer = ref(); const map = ref(null); @@ -38,6 +43,8 @@ const selectedGeoJsonLayerId = "selected-geojson-layer"; const currentDate = ref(new Date()); +const markerList = ref([]); + function initializeMap() { mapboxgl.accessToken = import.meta.env.VITE_MAPBOX_TOKEN; map.value = new mapboxgl.Map({ @@ -108,11 +115,15 @@ function onFeatureClick(e) { function onMouseEnterFeature() { map.value.getCanvas().style.cursor = "pointer"; } + function onMouseLeaveFeature() { map.value.getCanvas().style.cursor = ""; } + async function updateGeoJson() { const response = await MapAPI.getMapData(); + const geojsonData = response.data.geojson; + const alertData = response.data.alerts; if (map.value && map.value.getSource(geoJsonSourceId)) { map.value.removeLayer(geoJsonLayerId); @@ -121,7 +132,7 @@ async function updateGeoJson() { map.value.addSource(geoJsonSourceId, { type: "geojson", - data: response.data, + data: geojsonData, }); map.value.addLayer({ @@ -146,27 +157,21 @@ async function updateGeoJson() { features: [], }, }); - - /* - map.value.addLayer({ - id: selectedGeoJsonLayerId, - type: "line", - source: selectedGeoJsonSourceId, - paint: { - "line-color": "#ff0000", - "line-width": 5, - }, - }); - */ } currentGeoJson.value = response.data; map.value.on("click", geoJsonLayerId, onFeatureClick); map.value.on("mouseenter", geoJsonLayerId, onMouseEnterFeature); map.value.on("mouseleave", geoJsonLayerId, onMouseLeaveFeature); + + alertData.forEach((data) => { + createMarker(data); + }); } function updateMap() { + clearMarkers(); updateGeoJson().then(() => { + showMarkers(); currentDate.value = new Date(); ElNotification({ title: "Alerta", @@ -183,6 +188,68 @@ function getTime(date: Date) { return `${hours}:${minutes}:${seconds}`; } +function hideMarker(marker) { + marker.remove(); +} + +function showMarker(marker) { + marker.addTo(map.value); +} + +function createPopup(alertData: AlertData, marker) { + const keyValue = alertData.key_value; + const coords = alertData.coords; + const useful = alertData.useful; + const useless = alertData.useless; + + const popup = new mapboxgl.Popup({ + closeButton: true, + closeOnClick: false, + }) + .setLngLat(coords) + .setHTML( + "` + + "
" + + "
" + + "thumb_up" + + `${useful}` + + "
" + + "
" + + "thumb_down" + + `${useless}` + + "
" + + "
" + ); + hideMarker(marker); + popup.addTo(map.value); + popup.on("close", () => { + showMarker(marker); + }); +} + +function createMarker(alertData: AlertData) { + const coords = alertData.coords; + const marker = new mapboxgl.Marker({ color: "#FF4000" }).setLngLat(coords); + marker.getElement().addEventListener("click", () => { + createPopup(alertData, marker); + }); + markerList.value.push(marker); +} + +function showMarkers() { + markerList.value.forEach((marker) => { + marker.addTo(map.value); + }); +} + +function clearMarkers() { + markerList.value.forEach((marker) => { + marker.getElement().remove(); + }); + markerList.value = []; +} + onMounted(() => { initializeMap(); updateMap(); diff --git a/src/components/NavigationComponent.vue b/src/components/NavigationComponent.vue index 96579db..9c79a2b 100644 --- a/src/components/NavigationComponent.vue +++ b/src/components/NavigationComponent.vue @@ -50,7 +50,7 @@ const signOut = () => { arrow_forward_ios query_stats - + diff --git a/src/components/api/SpeedAPI.ts b/src/components/api/SpeedAPI.ts index 1346a8e..64a4196 100644 --- a/src/components/api/SpeedAPI.ts +++ b/src/components/api/SpeedAPI.ts @@ -1,7 +1,8 @@ import APIService from "@/components/api/APIService"; +import { parseDateObject } from "@/utils/date_utils"; class SpeedAPI { - public static getList( + public static getListByMonth( month: number, dayType: string | boolean, temporalSegment: number, @@ -14,12 +15,34 @@ class SpeedAPI { return APIService.get(endpoint, slug); } - public static getSpeeds(month: number, dayType: string | boolean, temporalSegment: number, page: number) { - return this.getList(month, dayType, temporalSegment, page, "geo/speeds"); + public static getListByDates( + startTime: string, + endTime: string, + dayType: string | boolean, + temporalSegment: number, + page: number, + endpoint: string + ) { + let slug = `?startTime=${startTime}&endTime=${endTime}&page=${page}`; + if (dayType !== false) slug += `&dayType=${dayType}`; + if (temporalSegment !== -1) slug += `&temporalSegment=${temporalSegment}`; + return APIService.get(endpoint, slug); + } + + public static getSpeeds( + startTime: Date, + endTime: Date, + dayType: string | boolean, + temporalSegment: number, + page: number + ) { + const parsedStartTime = parseDateObject(startTime); + const parsedEndTime = parseDateObject(endTime); + return this.getListByDates(parsedStartTime, parsedEndTime, dayType, temporalSegment, page, "geo/speeds"); } public static getHistoricSpeeds(month: number, dayType: string | boolean, temporalSegment: number, page: number) { - return this.getList(month, dayType, temporalSegment, page, "geo/historicSpeeds"); + return this.getListByMonth(month, dayType, temporalSegment, page, "geo/historicSpeeds"); } public static downloadCSV(month: number, dayType: string | boolean, temporalSegment: number, endpoint: string) { diff --git a/src/utils/date_utils.ts b/src/utils/date_utils.ts index 5b1389f..d78aae1 100644 --- a/src/utils/date_utils.ts +++ b/src/utils/date_utils.ts @@ -50,5 +50,17 @@ export function parseTemporalSegment(idx: number) { const start = formatTime(startHours, startMinutes); const end = formatTime(endHours, endMinutes); - return `${start} - ${end}`; + return `${start} - ${end} (${idx})`; +} + +export function parseDateObject(date: Date) { + const year = date.getFullYear(); + const month = (date.getMonth() + 1).toString().padStart(2, "0"); + const day = date.getDate().toString().padStart(2, "0"); + + const hours = date.getHours().toString().padStart(2, "0"); + const minutes = date.getMinutes().toString().padStart(2, "0"); + const seconds = date.getSeconds().toString().padStart(2, "0"); + + return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}Z`; } diff --git a/src/views/HistoricSpeedView.vue b/src/views/HistoricSpeedView.vue index a19d9f3..8539e34 100644 --- a/src/views/HistoricSpeedView.vue +++ b/src/views/HistoricSpeedView.vue @@ -36,6 +36,7 @@ const tableData = ref(); const totalCount = ref(0); const currentPage = ref(1); const totalPages = ref(1); + function downloadHistoricSpeeds(month: number, dayType: string | boolean, temporalSegment: number) { SpeedAPI.downloadHistoricSpeeds(month, dayType, temporalSegment).then((response) => { const url = window.URL.createObjectURL(new Blob([response.data])); @@ -86,11 +87,13 @@ function pageUp() { currentPage.value++; updateHistoricSpeedData(monthValue.value, dayTypeValue.value, selectedTemporalSegment.value); } + function pageDown() { if (currentPage.value == 1) return; currentPage.value--; updateHistoricSpeedData(monthValue.value, dayTypeValue.value, selectedTemporalSegment.value); } + function test(month: number, dayType: string | boolean) { console.log("passed month:", month); console.log("passed daytype", dayType); @@ -104,6 +107,9 @@ function test(month: number, dayType: string | boolean) {