# Project export: SNAPMap

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 11.0
- Tagline: SNAPMap visualizes U.S. stores that accept SNAP/EBT. Users enter a zip code and select store types (e.g., grocery, convenience) to find nearby locations that meet their needs.
- Devpost: https://devpost.com/software/snapmap-593heu
- GitHub: https://github.com/pdugar02/SNAPMap
- Team: 2 GitHub contributor(s) — Avy Harish (3 commits), pdugar02 (1 commits)

## Devpost submission (written by the team)

### Inspiration

: The inspiration behind SNAPMap came from the need to improve accessibility to essential services for low-income communities who rely on SNAP and EBT benefits. Finding stores that accept these benefits can be challenging, especially in underserved areas with little tech access. SNAPMap aims to make this process easier by providing a simple, user-friendly solution for these communities to quickly locate nearby grocery, convenience, and food stores that accept SNAP and EBT across the U.S.

### What it does

: SNAPMap helps users find grocery, convenience, and food stores across the U.S. that accept SNAP and EBT benefits. The app allows users to input a zip code and filter by store type, such as convenience stores, grocery stores, or supermarkets, to find locations near them. The interactive map visualizes the stores, making it easy to see where they can shop using their benefits. By providing this information in a user-friendly format, SNAPMap simplifies the process of locating SNAP/EBT-friendly stores, ensuring that users can easily access the food they need in their community despite economic and time hardships.

### How we built it

: We built SNAPMap using a combination of Python for backend development and data handling, and JavaScript for the interactive map. We sourced the dataset of SNAP and EBT-accepting locations from the United States Department of Agriculture (USDA) and used geocoding services to map them accurately. For the frontend, we used a clean, responsive design to ensure easy navigation. The filter functionality allows users to input their zip code and select the type of store they’re looking for, making the experience personalized and efficient.

### Challenges we ran into

: One of the main challenges was managing a large dataset and ensuring its accuracy and performance within the app. We also faced challenges in connecting the back-end which was written with JavaScript and the front end which was written with Python. Additionally, implementing a robust filtering system that provides accurate results without overwhelming the user with too many options required careful planning and testing.

### Accomplishments we're proud of

: One of our proudest accomplishments is creating an intuitive and accessible tool that has the potential to make a real impact for individuals and families relying on SNAP/EBT benefits. We successfully built an app that is easy to navigate, allowing users to quickly find stores that meet their needs based on location and store type—making sure accessibility is no longer a barrier. We’re also proud of the seamless integration of our interactive map and filtering features, which required handling a large dataset while ensuring a smooth user experience. Despite initial challenges, we were able to implement a user-friendly interface that works well without compromising on functionality or speed. Overall, we’re proud that we turned an idea into a functional, impactful app that addresses a real need in underserved communities.

### What we learned

: Throughout this project, we learned a lot about the complexities of mapping large datasets, working with geolocation tools, and managing user input for effective data filtering. We also learned how to ensure that a front end and back end can communicate with each other despite being written in different languages. Additionally, we gained valuable insight into the importance of clean data, usability, and providing a seamless user experience, particularly for audiences that may not be tech-savvy.

### What's next

: Looking ahead, we plan to expand SNAPMap’s features by introducing meal planning ideas based on the food available at stores that accept SNAP and EBT. Users will be able to see suggested meal plans tailored to their dietary preferences and the types of stores they’re searching for. By integrating data on available food items, SNAPMap could provide budget-friendly, nutritious meal options that align with SNAP benefits, making the shopping experience even more convenient and empowering users to make informed decisions about their food choices—-and making life easier for communities disadvantaged with economic and time constraints.

## README (from the GitHub repository)

# SNAPMap

## Detected evidence (automated analysis)

Indexed codebase: 22 recognized source files, 34 KB.
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Flask (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (31 of 31)

```
backend/app.py
frontend/.env
frontend/.gitignore
frontend/package.json
frontend/public/index.html
frontend/public/manifest.json
frontend/public/robots.txt
frontend/README.md
frontend/src/App.css
frontend/src/App.js
frontend/src/App.test.js
frontend/src/App2.css
frontend/src/App2.js
frontend/src/index.css
frontend/src/index.js
frontend/src/reportWebVitals.js
frontend/src/setupTests.js
react-here-map/.gitignore
react-here-map/package.json
react-here-map/public/index.html
react-here-map/public/manifest.json
react-here-map/public/robots.txt
react-here-map/README.md
react-here-map/src/App.css
react-here-map/src/App.js
react-here-map/src/App.test.js
react-here-map/src/index.css
react-here-map/src/index.js
react-here-map/src/reportWebVitals.js
react-here-map/src/setupTests.js
README.md
```

### Dependencies

- frontend/package.json: @testing-library/jest-dom@^5.17.0, @testing-library/react@^13.4.0, @testing-library/user-event@^13.5.0, react@^18.3.1, react-dom@^18.3.1, react-scripts@5.0.1, web-vitals@^2.1.4
- react-here-map/package.json: @testing-library/jest-dom@^5.17.0, @testing-library/react@^13.4.0, @testing-library/user-event@^13.5.0, react@^18.3.1, react-dom@^18.3.1, react-scripts@5.0.1, web-vitals@^2.1.4

### Recent commits (newest first)

- frontend + app.py backend
- comm
- comm
- Initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### react-here-map/package.json

```
{
  "name": "react-here-map",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

```

### frontend/package.json

```
{
  "name": "react-here-map",
  "proxy": "http://127.0.0.1:2000",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

```

### backend/app.py

```python
import pandas as pd
from flask import Flask, jsonify, request
from flask_cors import CORS  # Import CORS

app = Flask(__name__)
CORS(app)  # Enable CORS for the entire app

# Load the CSV data into a Pandas DataFrame
SNAP = pd.read_csv('SNAP.csv')

@app.route('/api/stores', methods=['GET'])
def get_stores():
    # Query parameter for filtering by ZIP Code
    zip_code = request.args.get('zip_code', None)
    print(f"ZIP code received: {zip_code}")  # Debugging statement

    # If ZIP code is provided, filter the DataFrame by the given ZIP code
    if zip_code:
        print(zip_code)
        filtered_data = SNAP[(SNAP['Latitude'] != 0) & (SNAP['Longitude'] != 0)]
        filtered_data = filtered_data.dropna(subset=['Latitude', 'Longitude'])
        filtered_data = filtered_data[
            (filtered_data['Latitude'].astype(str).str.strip() != '') & 
            (filtered_data['Longitude'].astype(str).str.strip() != '')
        ]
        filtered_data = filtered_data[filtered_data["Zip Code"].astype(str) == zip_code]
        # If no data is found for the given ZIP code, return an empty list
        if filtered_data.empty:
            print("No data found for ZIP code")
            return jsonify([]), 200
        
        # Extract only latitude and longitude columns
        coordinates = filtered_data[['Latitude', 'Longitude']].to_dict(orient='records')
        print(len(coordinates))
        # Return the coordinates as JSON
        print(jsonify(coordinates))
        return jsonify(coordinates), 200
    else:
        # If no ZIP code is provided, return an empty list
        return jsonify([]), 400

if __name__ == '__main__':
    # Run the Flask app
    app.run(debug=True, host='0.0.0.0', port=2000)

```

### react-here-map/src/index.js

```javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

```

### frontend/src/index.js

```javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  //<React.StrictMode>
    <App />
  //</React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

```

### frontend/src/App.js

```javascript
import React, { useEffect, useRef, useState } from "react";
import './App.css';  // Assume you have appropriate styles

function App() {
  const mapRef = useRef(null);  // Reference to the map container
  const [latitude, setLatitude] = useState(52.5159);  // Default latitude
  const [longitude, setLongitude] = useState(13.3777);  // Default longitude
  const [numPoints, setNumPoints] = useState(0);  // Number of markers based on response
  const [coordinatesList, setCoordinatesList] = useState([]);  // List of lat/lng from the backend
  const mapInstance = useRef(null);  // Reference to the map instance
  const platform = useRef(null);  // Reference to the HERE platform
  const [zipCode, setZipCode] = useState("");  // ZIP code input field

  useEffect(() => {
    if (!platform.current) {
      platform.current = new window.H.service.Platform({
        apikey: "jNg5nWiUmlAxaaN5LlpL3Eei6xm3_a2bTay2TRmiI2k",  
      });

      const defaultLayers = platform.current.createDefaultLayers();

      // Initialize the map
      mapInstance.current = new window.H.Map(
        mapRef.current,
        defaultLayers.vector.normal.map,
        {
          center: { lat: latitude, lng: longitude },
          zoom: 14,
          pixelRatio: window.devicePixelRatio || 1,
        }
      );

      const behavior = new window.H.mapevents.Behavior(new window.H.mapevents.MapEvents(mapInstance.current));
      const ui = window.H.ui.UI.createDefault(mapInstance.current, defaultLayers);
      window.addEventListener("resize", () => mapInstance.current.getViewPort().resize());
    }
  }, []);

  useEffect(() => {
    if (coordinatesList.length > 0) {
      updateMapMarkers(coordinatesList);
    } else {
      console.log("No coordinates to update on the map.");
    }
  }, [coordinatesList]);

  const updateMapMarkers = (newCoordinatesList) => {
    if (mapInstance.current) {
      // Clear existing markers
      mapInstance.current.removeObjects(mapInstance.current.getObjects());

      if (newCoordinatesList.length > 0) {
        newCoordinatesList.forEach(coord => {
          if (coord.Latitude && coord.Longitude) {
            console.log(coord)
            // Create a marker for each valid latitude and longitude
            const marker = new window.H.map.Marker({ lat: coord.Latitude, lng: coord.Longitude });
            mapInstance.current.addObject(marker);
          }
        });

        // Calculate the average latitude and longitude to set the map center
        const avgLat = newCoordinatesList.reduce((acc, val) => acc + val.Latitude, 0) / newCoordinatesList.length;
        const avgLng = newCoordinatesList.reduce((acc, val) => acc + val.Longitude, 0) / newCoordinatesList.length;

        // Set the map center and zoom level
        mapInstance.current.setCenter({ lat: avgLat, lng: avgLng });
        mapInstance.current.setZoom(14);
      } else {
        console.error("No valid coordinates to display.");
      }
    }
  };

  const handleSearch = (e) => {
    e.preventDefault();  // Prevent form from refreshing the page

    if (zipCode.trim()) {
      console.log(`Sending request with ZIP code: ${zipCode}`);

      // Pointing to the Flask backend
      fetch(`/api/stores?zip_code=${zipCode}`)
        .then(response => {
          console.log(`Received response with status: ${response.status}`);
          return response.json();
        })
        .then(data => {
          console.log("Received data:", data);
          if (data.length > 0) {
            console.log(`Number of locations found: ${data.length}`);
            setCoordinatesList(data);  // Set the coordinates in state
            setNumPoints(data.length);  // Update the number of points
          } else {
            console.log("No data found for this ZIP code.");
            alert("No data found for this ZIP code.");
          }
        })
        .catch(error => {
          console.error("Error fetching data:", error);
        });
    } else {
      console.log("No ZIP code entered");
    }
  };

  useEffect(() => {
    if (coordinatesList.length > 0) {
      console.log("coordinatesList has been updated:", coordinatesList);
      updateMapMarkers(coordinatesList); // Update markers whenever coordinatesList changes
    }
  }, [coordinatesList]);

  return (
    <div className="App">
      <nav className="navbar">
        <div className="brand">SNAPMap</div>
        <ul className="nav-links">
          <li><a href="#">Home</a></li>
          <li><a href="#">Find Stores</a></li>
          <li><a href="#">About</a></li>
        </ul>
      </nav>

      <header className="header">
        <h1>SNAP/EBT Shopping Made Easy</h1>
      </header>

      <form onSubmit={handleSearch}>
        <input
          type="text"
          placeholder="Enter Zip Code"
          value={zipCode}
          onChange={(e) => setZipCode(e.target.value)}
          className="zip-input"
        />
        <button type="submit" className="search-button">Search</button>
      </form>

      <div className="main-content">
        <div ref={mapRef} className="mapContainer">
          {/* The map will appear here */}
        </div>
      </div>

      <footer>
        <p>© 2024 SNAP Store Finder | <a href="#">Privacy Policy</a></p>
      </footer>
    </div>
  );
}

export default App;

```

### react-here-map/src/App.js

```javascript
import React, { useEffect, useRef, useState } from "react";
import './App.css';

function App() {
  // For Map and Marker Generation
  const mapRef = useRef(null); // Reference to the map container
  const [latitude, setLatitude] = useState(52.5159); // Default latitude (Berlin)
  const [longitude, setLongitude] = useState(13.3777); // Default longitude (Berlin)
  const [numPoints, setNumPoints] = useState(10); // Number of markers to generate
  const [coordinatesList, setCoordinatesList] = useState([]); // List of random coordinates
  const mapInstance = useRef(null); // Reference to the map instance
  const platform = useRef(null); // Reference to the HERE platform

  // For ZIP code and Location Type Selection
  const [zipCode, setZipCode] = useState("");
  const [locationType, setLocationType] = useState("All");

  // The list of location types
  const locationTypes = [
    "Convenience Store",
    "Small Grocery Store",
    "Combination Grocery/Other",
    "Medium Grocery Store",
    "Supermarket",
    "Super Store",
    "Meat/Poultry Specialty",
    "Bakery Specialty",
    "Large Grocery Store",
    "Farmers' Market",
    "Fruits/Veg Specialty",
    "Seafood Specialty",
    "Delivery Route",
    "Food Buying Co-op",
    "Wholesaler",
    "Military Commissary",
  ];

  // Function to generate random nearby coordinates
  const generateRandomCoordinates = (centerLat, centerLng, numPoints) => {
    const randomCoordinates = [];
    const radius = 0.01; // Define how close the random points should be (smaller = closer)

    for (let i = 0; i < numPoints; i++) {
      const randomLatOffset = (Math.random() - 0.5) * radius; // Random offset for latitude
      const randomLngOffset = (Math.random() - 0.5) * radius; // Random offset for longitude
      randomCoordinates.push({
        lat: centerLat + randomLatOffset,
        lng: centerLng + randomLngOffset,
      });
    }

    return randomCoordinates;
  };

  // Initialize the HERE Maps platform and map
  useEffect(() => {
    if (!platform.current) {
      platform.current = new window.H.service.Platform({
        apikey: "jNg5nWiUmlAxaaN5LlpL3Eei6xm3_a2bTay2TRmiI2k", // Replace with your HERE API key
      });

      const defaultLayers = platform.current.createDefaultLayers();

      // Initialize the map
      mapInstance.current = new window.H.Map(
        mapRef.current,
        defaultLayers.vector.normal.map,
        {
          center: { lat: latitude, lng: longitude },
          zoom: 14,
          pixelRatio: window.devicePixelRatio || 1, // Ensure high resolution on high DPI screens
        }
      );

      // Enable map behavior and interactions (panning, zooming)
      const behavior = new window.H.mapevents.Behavior(new window.H.mapevents.MapEvents(mapInstance.current));

      // Add default UI components (zoom buttons)
      const ui = window.H.ui.UI.createDefault(mapInstance.current, defaultLayers);

      // Ensure the map resizes with the window
      window.addEventListener("resize", () => mapInstance.current.getViewPort().resize());
    }
  }, []);

  // Function to update the map markers
  const updateMapMarkers = (newCoordinatesList) => {
    if (mapInstance.current) {
      // Remove any previous markers
      mapInstance.current.removeObjects(mapInstance.current.getObjects());

      // Add markers for the new coordinates
      newCoordinatesList.forEach(coord => {
        const marker = new window.H.map.Marker({ lat: coord.lat, lng: coord.lng });
        mapInstance.current.addObject(marker);
      });

      // Center the map to the new inputted location
      mapInstance.current.setCenter({ lat: latitude, lng: longitude });
      mapInstance.current.setZoom(14);

      // Ensure the map resizes properly
      mapInstance.current.getViewPort().resize();
    }
  };

  // Update the markers whenever latitude/longitude or coordinatesList changes
  useEffect(() => {
    updateMapMarkers(coordinatesList);
  }, [coordinatesList]);

  // Generate random points based on latitude, longitude, and numPoints
  const generateMarkers = () => {
    const newCoords = generateRandomCoordinates(latitude, longitude, numPoints);
    setCoordinatesList(newCoords);
  };

  // Handlers for updating latitude, longitude, and numPoints
  const handleLatChange = (e) => {
    const newLat = parseFloat(e.target.value);
    setLatitude(newLat);
  };

  const handleLngChange = (e) => {
    const newLng = parseFloat(e.target.value);
    setLongitude(newLng);
  };

  const handleNumPointsChange = (e) => {
    const newNumPoints = parseInt(e.target.value, 10);
    setNumPoints(newNumPoints);
  };

  // Handle ZIP code and location type search
  const handleSearch = (e) => {
    e.preventDefault();
    if (zipCode.trim()) {
      console.log("Searching for map at zip code:", zipCode);
      console.log("Filtering by location type:", locationType);
      // Add code here to perform the search or integrate with a map API based on zipCode and locationType
    }
  };

  return (
    <div className="App">
      {/* Navigation Bar */}
      <nav className="navbar">
        <div className="brand">SNAPMap</div>
        <ul className="nav-links">
          <li><a href="#">Home</a></li>
          <li><a href="#">Find Stores</a></li>
          <li><a href="#">About</a></li>
        </ul>
      </nav>

      {/* Header Section */}
      <header className="header">
        <h1>SNAP/EBT Shopping Made Easy</h1>
      </header>

      {/* Form for ZIP Code and Location Type */}
      <form onSubmit={handleSearch}>
        <input
          type="text"
          placeholder="Enter Zip Code"
          value={zipCode}
          onChange={(e) => setZipCode(e.target.value)}
          className="zip-input"
        />
        <select
          value={locationType}
          onChange={(e) => setLocationType(e.target.value)}
          className="location-filter"
        >
          <option value="All">All</option>
          {locationTypes.map((type, index) => (
      
[truncated — 1756 more characters]
```

### frontend/src/setupTests.js

```javascript
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

```

### react-here-map/src/setupTests.js

```javascript
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

```

### frontend/src/App.test.js

```javascript
import { render, screen } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  render(<App />);
  const linkElement = screen.getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});

```

[11 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]