diff --git a/.gitignore b/.gitignore index 964a0c6..b894980 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ tiles/ -tiles.tar \ No newline at end of file +tiles.tar +node_modules +map_tiles +public/ +dist/ +gps_webtransport_server/target/ \ No newline at end of file diff --git a/README.md b/README.md index b938213..26dc595 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,60 @@ # Map Server -This houses both the HTML/CSS/JS frontend to render the map with the rover's coordinates, -and the backend server that powers it. +This houses both the JS/vite frontend to render the map with the rover's coordinates, the home for generated map tiles, and a fake gps server for testing. For the backend of the map server, see the `auto_ros2` repository. ## Structure The frontend provides a leafletjs map which draws the rover's position on it. -The map renderer uses image files served by `server.py`, and generated by -the generating program in `RoverMapTileGenerator`. +The map renderer uses map tile image files created using QGIS software and stored locally in the `public` and `dist` folders. + +### Using QGIS to generate map tiles +**1. Generate an osm file using Open Street Maps** +First, you need to generate an osm file using Open Street Maps to ensure the correct extent for the map data. +Our current map uses the following bounds from the old map server: +``` javascript +// the lat/lon coords are the center of the map, 10 is the default zoom. +var map = L.map('map').setView([38.4375, -110.8125], 10); + +// These are the bounds of the rover team's map region for debugging. +new L.marker([38.4375, -110.8125]).addTo(map) +new L.marker([38.3750, -110.7500]).addTo(map) +``` +Go to [this link](https://www.openstreetmap.org/export#map=13/38.40107/-110.79815) to export an Open Street Map. Set the center as your desired center (in this case 38.4375, -110.8125), and set the bounds for the map as your desired bounds. Once the map has the correct bounds, export as an osm file, and save to your computer. +**2. Import to QGIS** +Download [QGIS](https://qgis.org/download/). Once downloaded, follow [these instructions](https://learnosm.org/en/osm-data/osm-in-qgis/) for importing an osm file. This should allow you to view the map on QGIS. +**3. Add satellite imagery** +Our current map uses Satellite Imagery on QGIS, which you can find instructions for adding to a map [here](https://gis.stackexchange.com/questions/439936/adding-google-satellite-imagery-to-qgis). +**4. Export map tiles** +Once the map looks the way you want it to, follow [these instructions](https://www.google.com/url?sa=t&source=web&rct=j&opi=89978449&url=https://www.youtube.com/watch%3Fv%3DvU4bGCh5khM&ved=2ahUKEwjVz6-KjsSNAxWhGDQIHbdHH0UQwqsBegQIEhAG&usg=AOvVaw0kwEDk3Qe84kvYYel7yQvp) to download the QTiles plugin and export your map as a set of map tiles. When choosing the directory to export your map tiles to, select `RoverMap/frontend/public`. +Once you upload new map tiles, don't forget to change the directory path in `frontend/map.js`. Also, if you want to use the tiles offline, make sure to rebuild the `dist/` folder (see offline instructions). + +This project uses `npm` and `vite` to manage package dependencies for Javascript and +Leaflet. To use the map server, make sure to [install npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). Once npm is installed, go to the frontend directory: +`cd frontend` +and run the following command to install project dependencies: +`npm install` ## Running and Integration -You can run the server in standalone mode by running `python3 server.py`. +### For Frontend +For online run, you can run `npx vite` to run the server's frontend. (Note: this requires for there to be map tiles in the `public` folder.) -To run it from python, import and call `start_map_server`. -The flask app isn't wrapped nicely in a class (this would be a good refactor), -so we really need to test this integration and make sure it works. +#### Offline Instructions +For offline, first make sure to run `npx vite build` for any new changes before +running offline. Then, given that the `dist` folder is properly populated, run +`npx serve dist` to serve the frontend offline. -The server sends the rover's gps coords to the web client, -so the server will need to be supplied with those coords. -Call `update_rover_coords` with an array of lat, lng like `[38.4065, -110.79147]`, -the center of the mars thingy. - -There's an example in `example/updater.py` to go off of. +### For Server +The server for this frontend is to be implemented in the `auto_ros2` repository. For testing purposes, a fake gps server is set up in `gps_webtransport_server`. To run the fake gps server, simply run: +``` +cd gps_webtransport_server +cargo run +``` +When you run this server in combination with the map server, it should generate a fake rover position on the map that alternates between two points. ## Accessing -The server should open to port 5000, so you can access by connecting to `10.0.0.2:5000`. -That port could change somehow, so you may need to check stdout to find the exact address and port. +The server should open to port 5173, so you can access by connecting to `https://localhost:5173/`. When you run `npx vite`, it should give you this link automatically. ## Documentation diff --git a/frontend/.gitignore b/frontend/.gitignore index c2658d7..b947077 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1 +1,2 @@ node_modules/ +dist/ diff --git a/frontend/circleTools.js b/frontend/circleTools.js new file mode 100644 index 0000000..199dda0 --- /dev/null +++ b/frontend/circleTools.js @@ -0,0 +1,97 @@ +import { map } from './map.js'; +import { toggleActivateButton } from './helpers.js'; + +let circles = []; +let labels = []; +let selectedCircle = null; +let isResizing = false; +let resizeHandle; +let activeTool = "none"; + +let circleList = document.getElementById('circleList'); +let createCircleButton = document.getElementById('toggleCircleButton'); + +export const toggleCircles = () => { + activeTool = (activeTool === 'circleDraw') ? 'none' : 'circleDraw'; + toggleActivateButton(createCircleButton, activeTool === 'circleDraw'); +}; + +export const clearCircles = () => { + circles.forEach(circle => circle.remove()); + labels.forEach(label => label.remove()); + circles = []; + labels = []; + updateCircleList(); +}; + +export function circleOnPoint() { + const input = prompt("Enter coordinates in `lat lon` format (e.g. 37.7749 -122.4194):")?.trim(); + if (!input) return; + + const [latStr, lonStr] = input.split(/\s+/); + const lat = parseFloat(latStr); + const lon = parseFloat(lonStr); + + if (isNaN(lat) || isNaN(lon)) { + alert("Invalid coordinates. Please enter valid numbers."); + return; + } + + const isConfirmed = confirm(`Create a circle at latitude: ${lat}, longitude: ${lon}?`); + if (!isConfirmed) return; + + const center = L.latLng(lat, lon); + const circle = L.circle(center, { radius: 50, color: "blue" }).addTo(map); + circles.push(circle); + + const label = L.marker(center, { opacity: 0 }); + label.bindTooltip(`${circles.length}`, { + permanent: true, className: "circleLabel", direction: 'top', offset: [-15, 20] + }).addTo(map); + labels.push(label); + + updateCircleList(); +} + +function updateCircleList() { + circleList.innerHTML = '
${index + 1}. Radius: ${radius}m
Circumference: ${2 * radius}m
${el.value}
`; +}; + +let open = false; +export const openUnitConverter = () => { + if (!open) { + document.getElementById("unitConverter").style.display = "block"; + open = true; + } else { + closeUnitConverter(); + } +}; + +export const closeUnitConverter = () => { + document.getElementById("unitConverter").style.display = "none"; + open = false; +}; + +export const addWayPoint = (map, pathMarkers) => { + const coordinates = prompt("Enter your coordinate in `lat lon` format")?.split(" "); + if (!coordinates || coordinates.length !== 2) return; + + const lat = parseFloat(coordinates[0]); + const lng = parseFloat(coordinates[1]); + + const isConfirmed = confirm(`Add indicator at lat: ${lat}, lng: ${lng}?`); + if (isConfirmed) { + const marker = L.marker([lat, lng]).addTo(map); + pathMarkers.push(marker); + } +}; diff --git a/frontend/index.css b/frontend/index.css index b20a371..c286731 100644 --- a/frontend/index.css +++ b/frontend/index.css @@ -125,4 +125,39 @@ h2 { #pathOutput { flex: 1; padding: 1em; +} + +.gps-marker { + position: relative; + width: 20px; + height: 20px; + border-radius: 50%; + background-color: transparent; +} + +.gps-pulse { + width: 20px; + height: 20px; + background-color: rgba(0, 128, 255, 0.5); + border: 2px solid #0080ff; + border-radius: 50%; + animation: pulse 2s infinite; + box-sizing: border-box; +} + +@keyframes pulse { + 0% { + transform: scale(0.9); + opacity: 1; + } + + 70% { + transform: scale(1.5); + opacity: 0; + } + + 100% { + transform: scale(0.9); + opacity: 0; + } } \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index 376d9ca..dc0c823 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,20 +4,17 @@ -Decimal values previously copied: