A civic tech platform for exploring property, parcel, and election data in Montgomery County, Ohio
Monty Lots is an open-source geospatial data platform built by Code for Dayton to make property, parcel, and election information accessible, searchable, and useful for residents, researchers, civic organizations, and Democracy Fellows.
- π Intelligent Search - Search properties by address or parcel ID with real-time filtering
- π³οΈ Election Data Visualization - Interactive maps of 2024 and 2025 election results by precinct
- π Large Dataset Handling - Automatic clustering for datasets with 1000+ features
- π― Manual Layer Loading - User-controlled layer loading to optimize performance
- πΊοΈ Interactive Mapping - Full-screen Leaflet interface with dynamic layer discovery
- π Shareable URLs - Search results can be bookmarked and shared via URL parameters
- π± Responsive Design - Works seamlessly on desktop, tablet, and mobile devices
- π Voter Turnout Choropleth - Visualize turnout rates with 8-tier color scale (red to green)
- βοΈ Comparison Mode - Side-by-side comparison of 2024 vs 2025 elections with diverging colors
- π Advanced Filtering - Filter precincts by name, turnout range, or race winner
- π Full Election History - View complete race results for any precinct
- π¨ Dynamic Race Selection - Browse and visualize any race from 180+ available contests
- π Summary Statistics - Average, min, and max turnout across all precincts
- Dynamic Service Discovery - Automatically catalogs all GeoJSON files in the data directory
- GeoJSON Validation - Automatic validation of GeoJSON syntax with detailed error reporting
- S3-Compatible Storage - Optional sync from DigitalOcean Spaces, AWS S3, Backblaze B2, or any S3-compatible storage
- Koop FeatureServer API - Industry-standard GIS REST API for data access
- Layer Visibility Controls - Toggle layers on/off without reloading
- Real-time Status Indicators - Visual feedback for loading states and feature counts
- Health Check Endpoint - Monitoring-ready
/healthendpoint for production deployments
- Node.js v14 or higher
- npm (comes with Node.js)
-
Clone the repository
git clone https://github.com/codefordayton/new_monty_lots.git cd new_monty_lots -
Install dependencies
npm install
-
Start the server
npm start
-
Open in your browser
- Navigate to http://localhost:8080
- The map interface will load, showing available layers in the sidebar
For active development with auto-restart on file changes:
npm run dev- Load Layers - Click the "Load" button next to any layer in the sidebar to display it on the map
- Search Properties - Use the search box to filter by address or parcel ID
- View Details - Click any feature on the map to see its properties in a popup
- Share Results - Copy the URL to share specific search results with others
- Select Election Year - Choose between 2024 and 2025 elections
- Choose a Race - Select from dropdown of 180+ races or view voter turnout
- View Results - Precinct boundaries are colored by vote percentage or turnout
- Compare Years - Toggle "Comparison Mode" to see changes between 2024 and 2025
- Filter Precincts - Use advanced filters to search by name, turnout, or winner
- View History - Click any precinct and select "View Full Election History"
The search function supports:
- Address search - Enter full or partial street addresses
- Parcel ID search - Search by tax parcel numbers
- Real-time filtering - Results update as you type (300ms debounce)
- Result counts - Shows matching features out of total dataset
The application automatically optimizes for dataset size:
- < 1,000 features - Standard Leaflet layers
- > 1,000 features - Marker clustering enabled
- > 5,000 features - Warning displayed, clustering highly optimized
- Manual loading - Users control which layers load to manage memory
Backend:
- Koop v10.4.17 - GIS data transformation framework
- @koopjs/provider-file-geojson - GeoJSON file serving
- Express.js (via Koop) - Web server framework
Frontend:
- Leaflet v1.9.4 - Interactive mapping library
- Leaflet.markercluster - Marker clustering
- Vanilla JavaScript - No framework dependencies
- Responsive CSS - Mobile-first design
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Client Browser β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Leaflet Map Interface (index.html) β β
β β β’ Dynamic layer loading β β
β β β’ Search & filter UI β β
β β β’ Marker clustering β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β HTTP/JSON
βββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββ
β Koop Server (index.js) β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Custom Endpoints: β β
β β β’ GET / β Serve frontend β β
β β β’ GET /catalog β List all services β β
β β β’ GET /health β Health check β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Koop FeatureServer API: β β
β β β’ /file-geojson/rest/services/{layer}/FeatureServer β
β β β’ /file-geojson/rest/services/{layer}/FeatureServer/0/query β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β File I/O
βββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββ
β provider-data/ Directory β
β β’ housing.geojson (47 MB) β
β β’ registry.geojson (19 MB) β
β β’ [any .geojson files are auto-discovered] β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- Manual Layer Loading - Users explicitly load layers to prevent overwhelming browser memory with large datasets
- Automatic Clustering - Point features in large datasets (>1000 features) automatically use clustering
- Zero Configuration - Drop any
.geojsonfile inprovider-data/and it's automatically served - Service Catalog Pattern - Custom
/catalogendpoint provides metadata about all available layers - Progressive Enhancement - Core functionality works without JavaScript, enhanced with interactivity
GET /catalog
Returns metadata about all available GeoJSON services.
Response:
{
"services": [
{
"id": "housing",
"name": "Housing",
"type": "FeatureServer",
"url": "/file-geojson/rest/services/housing/FeatureServer",
"queryUrl": "/file-geojson/rest/services/housing/FeatureServer/0/query"
}
],
"count": 2
}GET /file-geojson/rest/services/{layer}/FeatureServer
Returns service metadata for a specific layer.
GET /file-geojson/rest/services/{layer}/FeatureServer/0/query
Query features from a layer.
Query Parameters:
f=geojson- Return format (geojson, json)where=1=1- SQL-style where clauseoutFields=*- Fields to returnreturnCountOnly=true- Return only feature count
Example:
# Get all features as GeoJSON
curl "http://localhost:8080/file-geojson/rest/services/housing/FeatureServer/0/query?f=geojson&where=1=1&outFields=*"
# Get feature count only
curl "http://localhost:8080/file-geojson/rest/services/housing/FeatureServer/0/query?f=json&where=1=1&returnCountOnly=true"GET /health
Returns server health status for monitoring.
Response:
{
"status": "healthy",
"timestamp": "2025-11-10T12:34:56.789Z"
}GET /api/sync/status
Returns current S3 sync configuration status.
Response:
{
"enabled": true,
"bucket": "my-geojson-data",
"endpoint": "https://nyc3.digitaloceanspaces.com",
"autoSync": true,
"syncInterval": 0
}POST /api/sync
Manually trigger a sync from S3 storage. Only available when S3_ENABLED=true.
Response:
{
"success": true,
"filesSync": 2,
"totalFiles": 2
}new_monty_lots/
βββ index.js # Koop server with custom endpoints
βββ index.html # Frontend map interface
βββ static/ # Frontend assets
β βββ scripts/
β β βββ components/ # UI components (ElectionUI, Filters, etc.)
β β βββ services/ # Data services (Catalog, Layer, Style)
β β βββ state/ # State management (MapState, LayerState)
β β βββ utils/ # Utilities (popupBuilder, fieldAnalyzer)
β β βββ main.js # Application entry point
β βββ styles/
β βββ main.css # Application styles
βββ lib/
β βββ s3-sync.js # S3-compatible storage sync module
βββ config/ # Layer styling configuration
β βββ styles.json # Style configuration index
β βββ layers/ # Layer-specific styling rules
βββ data/ # Election and property data
β βββ README.md # Data organization documentation
β βββ raw/ # Raw source data (PDFs, CSVs)
β βββ elections/ # Processed election JSON files
βββ docs/
β βββ DIGITALOCEAN_SPACES_SETUP.md # S3 storage setup guide
βββ provider-data/ # GeoJSON files served via Koop
β βββ housing.geojson # Housing/property data (47MB)
β βββ registry.geojson # Registry data (19MB)
β βββ precincts.geojson # Base precinct boundaries
β βββ precincts_2024.geojson # 2024 election results (13MB)
β βββ precincts_2025.geojson # 2025 election results (13MB)
βββ .env.example # Environment variable template
βββ package.json # Dependencies and scripts
βββ README.md # This file
βββ CONTRIBUTING.md # Contribution guidelines
βββ CLAUDE.md # AI assistant documentation
βββ deploy.md # Deployment guide
Simply add .geojson files to the provider-data/ directory:
# Copy your GeoJSON file
cp my-data.geojson provider-data/
# Restart the server (or nodemon will auto-restart)
npm startThe new layer will automatically:
- Appear in the
/catalogendpoint - Show up in the frontend sidebar
- Be queryable via the Koop API
- Use the filename (without extension) as the layer ID
For production deployments or large datasets, you can store GeoJSON files in S3-compatible object storage:
Supported providers:
- DigitalOcean Spaces
- AWS S3
- Backblaze B2
- Wasabi
- MinIO (self-hosted)
- Any S3-compatible storage
Setup:
-
Copy the environment example
cp .env.example .env
-
Configure your S3 settings in
.envS3_ENABLED=true S3_BUCKET=my-geojson-data S3_ENDPOINT=https://nyc3.digitaloceanspaces.com # For DigitalOcean Spaces S3_ACCESS_KEY_ID=your-access-key S3_SECRET_ACCESS_KEY=your-secret-key -
Start the server
npm start # Files will automatically sync from S3 to local cache on startup
Features:
- β Automatic sync on server startup
- β Optional periodic sync at configurable intervals
- β
Manual sync via API:
POST /api/sync - β
Check sync status:
GET /api/sync/status - β All files are validated before serving
- β Works seamlessly with existing Koop provider
See docs/DIGITALOCEAN_SPACES_SETUP.md for detailed setup instructions.
- Format: Valid GeoJSON (FeatureCollection recommended)
- Coordinate System: WGS84 (EPSG:4326) or Web Mercator (EPSG:3857)
- File Size: No strict limit with S3 storage; < 50MB for local-only hosting
- Naming: Use lowercase, hyphens for spaces (e.g.,
property-parcels.geojson) - Validation: All files are automatically validated for correct GeoJSON syntax
Railway (Recommended for quick deployments)
# Install Railway CLI
npm i -g @railway/cli
# Deploy
railway upRender
- Connect your GitHub repository
- Set build command:
npm install - Set start command:
npm start - Deploy automatically on push
DigitalOcean App Platform
- Create new app from GitHub
- Configure as Node.js service
- Set environment variable
PORT=8080
See deploy.md for detailed deployment instructions, including:
- Environment variable configuration
- Object storage integration (AWS S3, GCS, Azure)
- Scaling considerations
- Performance optimization
We welcome contributions from developers, designers, GIS professionals, and civic tech enthusiasts!
Ways to contribute:
- π Report bugs and request features via GitHub Issues
- π» Submit pull requests for bug fixes or new features
- π Improve documentation
- π§ͺ Test with different datasets and provide feedback
- π¨ Enhance UI/UX design
IMPORTANT: Always create a feature branch for your work. Never commit directly to main.
-
Create a feature branch
git checkout -b feature/your-feature-name # or for bug fixes: git checkout -b fix/issue-number-description -
Make your changes and commit
git add . git commit -m "Description of your changes"
-
Push to your branch
git push origin feature/your-feature-name
-
Open a Pull Request
- Go to GitHub and create a PR from your branch to
main - Link any related issues
- Request review from maintainers
- Go to GitHub and create a PR from your branch to
Please read CONTRIBUTING.md for detailed guidelines on:
- Development workflow
- Code style and standards
- Testing procedures
- Pull request process
v2.0 - Enhanced Search & Analytics
- Advanced spatial queries (radius search, polygon selection)
- Property comparison tool
- Export search results to CSV/GeoJSON
- Custom layer styling via UI
v3.0 - Data Integration
- Connect to live county data APIs
- Automatic data refresh/sync
- Historical data timeline view
- Multi-county support
v4.0 - Collaboration & Community
- User accounts and saved searches
- Community annotations and notes
- Public/private layer sharing
- Embeddable map widgets
- Unit and integration tests
- TypeScript migration
- GraphQL API option
- WebSocket support for real-time updates
- Vector tile serving for better performance
- Offline PWA capabilities
For Residents:
- Research properties before purchase or rental
- Understand neighborhood boundaries and zoning
- Track property development in their area
- Explore election results in their precinct
- Compare turnout and voting patterns over time
For Researchers & Democracy Fellows:
- Analyze property and election patterns across Montgomery County
- Study voter turnout trends by precinct and demographic area
- Identify geographic patterns in election results
- Compare year-over-year changes in voter participation
- Export data for academic research and civic analysis
For Civic Organizations:
- Identify underutilized properties
- Plan community development projects
- Support affordable housing initiatives
- Understand voting patterns for outreach campaigns
- Target areas with low voter turnout
For Developers:
- Build applications on top of the API
- Integrate property and election data into other civic tech tools
- Create custom visualizations and dashboards
- Access 180+ race results via structured JSON
- Code for Dayton - Civic tech volunteer organization building this platform
- Democracy Fellows - Supporting civic engagement through election data transparency
- Montgomery County Board of Elections - Providing comprehensive election results data
- Montgomery County, Ohio - Open data access for property records
- Koop Team - Excellent open-source GIS data transformation framework
- OpenStreetMap - Map tile data and geographic basemaps
This project is licensed under the ISC License - see the LICENSE file for details.
- GitHub Issues: Report bugs or request features
- Code for Dayton: Join our community
- Email: info@codefordayton.org
Built with β€οΈ by Code for Dayton volunteers
Making public data public and accessible for everyone