Skip to main content

Overview

The Location Manager handles geocoding, location persistence, and CRUD operations for favorite locations. It integrates with the backend API and Nominatim for geocoding services.

API Configuration

The application dynamically configures API endpoints based on the environment:
assets/js/api/config.js
export var pythonURI;
if (location.hostname === "localhost") {
  pythonURI = "http://localhost:8888";
} else if (location.hostname === "127.0.0.1") {
  pythonURI = "http://127.0.0.1:8888";
} else {
  pythonURI = "https://autonomous.stu.nighthawkcodingsociety.com";
}

export const fetchOptions = {
  method: 'GET',
  mode: 'cors',
  cache: 'default',
  credentials: 'include',
  headers: {
    'Content-Type': 'application/json',
    'X-Origin': 'client'
  },
};
The credentials: 'include' setting ensures cookies are sent with cross-origin requests for authentication.

Geocoding with Nominatim

Nominatim converts location names to geographic coordinates:
navigation/dailyroutine.html
async function geocodePlace(place) {
  const response = await fetch(
    `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(place)}`
  );
  const data = await response.json();
  return data[0];
}

Response Structure

{
  "lat": "32.7157",
  "lon": "-117.1611",
  "display_name": "San Diego, California, USA",
  "type": "city"
}

Usage Example

const location = await geocodePlace("San Diego");
const latLng = L.latLng(parseFloat(location.lat), parseFloat(location.lon));

Favorite Locations CRUD

Create Location

Add a new favorite location to the database:
navigation/favoriteLocations/favoriteLocations.js
async function createScores(inputName, inputAddress) {
  const locationData = {
    address: inputAddress,
    name: inputName
  };

  try {
    const response = await fetch(`${pythonURI}/api/saved_locations`, {
      ...fetchOptions,
      method: 'POST',
      body: JSON.stringify(locationData),
    });

    if (!response.ok) {
      throw new Error(`Failed to submit location: ${response.statusText}`);
    }

    const result = await response.json();
    return (result);

  } catch (error) {
    console.error('Error submitting location:', error);
    alert('Error submitting location: ' + error.message);
    return null;
  }
}

Read Locations

Fetch all saved locations from the API:
navigation/favoriteLocations/favoriteLocations.js
async function readScores() {
  try {
    const scoresResponse = await fetch(`${pythonURI}/api/saved_locations`, fetchOptions);
    if (!scoresResponse.ok) throw new Error('Failed to fetch locations');
    const scores = await scoresResponse.json();

    return (scores);

  } catch (error) {
    console.error('Error fetching locations:', error);
    alert('Error fetching locations: ' + error.message);
    return null;
  }
}

Update Location

Modify an existing favorite location:
navigation/favoriteLocations/favoriteLocations.js
async function updateScores(inputId, inputAddress, inputName) {
  const scoreData = {
    id: inputId,
    address: inputAddress,
    name: inputName
  }

  try {
    const response = await fetch(`${pythonURI}/api/saved_locations`, {
      ...fetchOptions,
      method: 'PUT',
      body: JSON.stringify(scoreData),
    });

    if (!response.ok) {
      throw new Error(`Failed to update location: ${response.statusText}`);
    }

    const result = await response.json();
    return (result);
  }

  catch (error) {
    console.error('Error updating location:', error);
    alert('Error updating location: ' + error.message);
  }
}

Delete Location

Remove a favorite location:
navigation/favoriteLocations/favoriteLocations.js
async function deleteScores(inputId) {
  const scoreData = {
    id: inputId
  }

  try {
    const response = await fetch(`${pythonURI}/api/saved_locations`, {
      ...fetchOptions,
      method: 'DELETE',
      body: JSON.stringify(scoreData),
    });

    if (!response.ok) {
      throw new Error(`Failed to delete location: ${response.statusText}`);
    }

    const result = await response.json();
    return (result);

  }
  catch (error) {
    console.error('Error deleting location:', error);
    alert('Error deleting location: ' + error.message);
    return null;
  }
}

User Filtering

Filter locations for the current authenticated user:
navigation/favoriteLocations/favoriteLocations.js
async function filterForUsername(scores) {
  const currentUserResponse = await fetch(`${pythonURI}/api/user`, fetchOptions);
  if (!currentUserResponse.ok) throw new Error('Failed to fetch current user');
  const currentUser = await currentUserResponse.json();
  let userName = currentUser.name;

  return (scores.filter((entry) => String(entry.username) === String(userName)));
}

UI Population

Display locations in a grid layout:
navigation/favoriteLocations/favoriteLocations.js
async function populateScores() {
  const grid = document.getElementById('locations-grid');
  grid.innerHTML = ''; // Clear existing content

  let scores = await filterForUsername(await readScores());

  scores.forEach(location => {
    const square = document.createElement('button');
    square.className = 'location-square';
    square.textContent = location.user_name;
    square.addEventListener('click', () => showPopup(location));
    grid.appendChild(square);
  });
}

Interactive Popups

Create dynamic popups for location details:
navigation/favoriteLocations/favoriteLocations.js
function showPopup(location) {
  const popup = document.createElement('div');
  popup.className = 'popup';

  popup.innerHTML = `
    <div class="popup-content">
      <button class="popup-close">&times;</button>
      <p><strong>Name:</strong> <span id="location-name">${location.user_name}</span></p>
      <p><strong>Address:</strong> <span id="location-address">${location.user_address}</span></p>
      <div>
        <button class="popup-delete">Delete</button>
        <button class="popup-edit">Edit</button>
      </div>
    </div>
  `;

  popup.querySelector('.popup-close').addEventListener('click', () => {
    document.body.removeChild(popup);
  });

  popup.querySelector('.popup-delete').addEventListener('click', async () => {
    await deleteScores(location.id);
    document.body.removeChild(popup);
    populateScores();
  });

  document.body.appendChild(popup);
}

Inline Editing

Toggle between view and edit modes:
navigation/favoriteLocations/favoriteLocations.js
const editButton = popup.querySelector('.popup-edit');
editButton.addEventListener('click', () => {
  const nameSpan = document.getElementById('location-name');
  const addressSpan = document.getElementById('location-address');

  if (editButton.textContent === 'Edit') {
    nameSpan.innerHTML = `<input type="text" id="edit-location-name" value="${location.user_name}" />`;
    addressSpan.innerHTML = `<input type="text" id="edit-location-address" value="${location.user_address}" />`;
    editButton.textContent = 'Submit';
  } else {
    const updatedName = document.getElementById('edit-location-name').value.trim();
    const updatedAddress = document.getElementById('edit-location-address').value.trim();

    if (updatedName && updatedAddress) {
      updateScores(location.id, updatedAddress, updatedName).then(() => {
        document.body.removeChild(popup);
        populateScores();
      }).catch(error => {
        alert('Error updating location: ' + error.message);
      });
    } else {
      alert('Please fill in both fields.');
    }
  }
});

Local Storage Integration

Daily Routine Schedule

Store schedule data locally:
navigation/dailyroutine.html
let scheduleData = JSON.parse(localStorage.getItem('dailySchedule')) || {};

function saveSchedule() {
  localStorage.setItem('dailySchedule', JSON.stringify(scheduleData));
}

Best Practices

Always use the dynamic API URI configuration:
import { pythonURI, fetchOptions } from '../../assets/js/api/config.js';
Provide user-friendly error messages:
if (!response.ok) {
  throw new Error(`Failed to update location: ${response.statusText}`);
}
Always validate user input before API calls:
if (!updatedName || !updatedAddress) {
  alert('Please fill in both fields.');
  return;
}
Encode location strings for geocoding:
const url = `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(place)}`;

API Endpoints

/api/saved_locations
POST, GET, PUT, DELETE
CRUD operations for favorite locations
/api/user
GET
Fetch current authenticated user information
/api/get_routes
POST
Calculate routes with traffic data

Map Integration

Learn about Leaflet map setup and configuration

Routing Engine

Explore route calculation and rendering

Build docs developers (and LLMs) love