diff --git a/.env.example b/.env.example index 4001e71..a3470b6 100644 --- a/.env.example +++ b/.env.example @@ -31,3 +31,6 @@ AWS_STORAGE_BUCKET_NAME= AWS_S3_REGION_NAME=auto AWS_S3_ENDPOINT_URL= AWS_S3_CUSTOM_DOMAIN= + +# Optional Redis URL for rate limiting cache (defaults to in-memory). +REDIS_URL= diff --git a/AGENTS.md b/AGENTS.md index 31b4ed2..ae757bd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,21 +4,24 @@ Project-specific guide for coding agents and contributors working on this reposi ## 1) Mission -Maintain and evolve the Upper Cumberland CleanUp Map MVP with minimal regressions. +Build and maintain a production-ready interactive District 3 (Putnam County Commission) CleanUp Map with two core modes: **Report Trash** and **Cleanup Trash**. Key priorities: -- Keep the app fully functional for logged-in users. +- Public map viewing; login required to submit reports or cleanups. +- District geo-data abstracted for future multi-district support. +- Anti-abuse: rate limiting + IP banning. +- Modern, accessible, mobile-first UI/UX. - Preserve geospatial correctness (`SRID 4326`, `[lng, lat]` order). -- Keep changes small, testable, and documented. ## 2) Stack and Boundaries - Django templates + vanilla JS frontend only. - GeoDjango + PostGIS backend. - No React/Next.js in this repo. -- Media files stored locally in development. -- Auth required for all map/API routes. +- Media files stored locally in development, S3/R2 in production. +- `django-ratelimit` for API throttling. +- Auth required for POST/PATCH endpoints only. ## 3) Ground Rules for Changes @@ -26,116 +29,64 @@ Key priorities: 2. Do not change coordinate order from `[lng, lat]`. 3. Keep geometry fields as `geography=True`, `srid=4326`. 4. If models change, include migration files. -5. If API payloads change, update: - - `geoapp/views.py` - - frontend JS usage in `static/js/map.js` - - docs in `README.md` +5. If API payloads change, update views, frontend JS, and docs. 6. Add or update tests for behavioral changes. -7. Keep auth protection on map and API routes. +7. Keep auth protection on write endpoints (create, update, mark-cleaned, feedback). +8. Public read endpoints (features, districts, detail, cleanups) must remain unauthenticated. +9. Rate limits must be preserved on all API endpoints. -## 4) Workflow for Feature Work +## 4) File Ownership Map -1. Create branch: - -```powershell -git checkout -b feat/ -``` - -2. Implement in this sequence: - -- data model -- migration -- API logic -- frontend integration -- tests -- docs - -3. Verify in Docker: - -```powershell -docker compose up -d -docker compose exec web python manage.py makemigrations -docker compose exec web python manage.py migrate -docker compose exec web python manage.py test -``` - -4. Manual smoke test: - -- Login works. -- `/map/` loads. -- Add trash pin. -- Mark cleaned with proof. -- Draw and save route. -- Filter by status/date. - -## 5) File Ownership Map (Practical) - -- `geoapp/models.py`: schema and geometry behavior -- `geoapp/views.py`: JSON API and serialization +- `geoapp/models.py`: schema and geometry behavior (District, TrashSite, IPBan, etc.) +- `geoapp/views.py`: JSON API and template views - `geoapp/urls.py`: route contracts -- `templates/geoapp/map.html`: map controls/modals layout -- `static/js/map.js`: map interactions and AJAX calls +- `geoapp/validators.py`: upload validation +- `geoapp/middleware.py`: IP ban enforcement +- `geoapp/permissions.py`: role/permission checks +- `geoapp/services.py`: activity logging, district assignment +- `templates/geoapp/map.html`: two-mode map UI +- `templates/geoapp/cleanups.html`: public cleanup showcase +- `static/js/map.js`: map interactions and AJAX +- `static/js/utils.js`: shared utilities (toast, modal, CSRF, fetch) +- `static/js/base.js`: feedback modal +- `static/css/palette.css`: design tokens - `geoapp/tests.py`: critical behavior validation - `docker-compose.yml`, `Dockerfile`: local runtime -- `README.md`: operator/developer documentation -## 6) API/Frontend Consistency Checklist +## 5) API/Frontend Consistency Checklist When adding or renaming fields, update all of: -1. model field definition -2. serializer output in views -3. endpoint input parsing/validation -4. frontend form controls -5. frontend fetch payload construction -6. detail/popup rendering -7. tests -8. README endpoint docs/examples +1. Model field definition +2. Serializer output in views +3. Endpoint input parsing/validation +4. Frontend form controls +5. Frontend fetch payload construction +6. Detail/popup rendering +7. Tests +8. README endpoint docs -## 7) Common Pitfalls to Avoid +## 6) Common Pitfalls - Using `[lat, lng]` accidentally in stored data. -- Returning geometry not compatible with Leaflet renderer. - Forgetting CSRF header on POST/PATCH requests. - Adding model fields without migration. -- Filtering only one feature type when both types should be filtered. -- Breaking Docker startup by changing container command without migration step. - -## 8) Testing Expectations +- Breaking the two-mode UI state management in map.js. +- Not validating photo uploads server-side. +- Forgetting to update rate limit decorators on new endpoints. -At minimum before finalizing: +## 7) Testing Expectations - `python manage.py test` passes in container. -- Existing tests still pass after schema/API changes. -- Added logic includes at least one targeted test. +- Existing tests still pass after changes. +- New logic includes at least one targeted test. +- Rate limit tests use `@override_settings(RATELIMIT_ENABLE=False)` when not testing limits. -Suggested extra checks: - -- verify `/api/features/` with bbox and days filters -- verify photo uploads render in detail views -- verify distance recalculation on route update - -## 9) Documentation Policy +## 8) Documentation Policy Any of the following requires README update: - -- new env vars -- endpoint path or payload changes -- setup workflow changes +- New env vars +- Endpoint path or payload changes +- Setup workflow changes - Docker behavior changes -- auth or permission model changes - -Every feature PR must also update: - -- `docs/FEATURES.md` (current feature behavior + acceptance criteria) -- `docs/QA_MATRIX.md` (coverage status, links, and notes) - -All manual verification steps must be recorded in: - -- `docs/MANUAL_TESTS.md` - -## 10) Future Direction (Keep in Mind) - -- Preserve invite-only workflow unless explicitly changed. -- Keep map interactions lightweight and responsive. -- Prefer incremental enhancements over large rewrites. +- Auth or permission model changes diff --git a/README.md b/README.md index 7cc3f43..d3d1447 100644 --- a/README.md +++ b/README.md @@ -1,644 +1,125 @@ -# Upper Cumberland CleanUp Map (Django + GeoDjango) +# Upper-Cumberland CleanUp -All-Django web app for tracking trash sites and cleanup routes in Putnam County, TN. +Interactive web app for reporting trash and organizing cleanup efforts in the Upper Cumberland region of Tennessee, starting with Putnam County Commission District 3. - Backend: Django + GeoDjango + PostGIS - Frontend: Django templates + Leaflet + leaflet-draw + vanilla JS -- Auth: login required for all map and API access (invite-only via admin user creation) +- Auth: public map viewing; login required to submit reports/cleanups +- Anti-abuse: django-ratelimit + IP ban middleware -## Sample Screens +## Demo -### Desktop Map +### Report Mode — Site Detail with Area Polygon +![Report mode with area polygon and site detail](images/Demo_1.png) -![Desktop map overview](docs/screenshots/map-overview-desktop.png) +### Completed Cleanups Page +![Public cleanups showcase](images/Demo_2.png) -### Desktop Impact Dashboard +### District 3 Boundary with Site Detail & Popup +![District 3 boundary with trash site detail and mark cleaned popup](images/Demo_3.png) -![Desktop impact dashboard](docs/screenshots/impact-desktop.png) +### Cleanup Mode — Draw Area +![Cleanup mode with draw area tool active](images/Demo_4.png) -### Desktop Updates Feed +## Core Features -![Desktop updates feed](docs/screenshots/updates-desktop.png) +### Two-Mode Map UI +- **Report Trash**: Place a pin or draw a polygon area, add description/severity/photos (max 5) +- **Cleanup Trash**: Click active reports, submit before/after photos and cleanup proof -### iPhone Map +### Public Pages +- `/` — Interactive map with Putnam County boundary and District 3 overlay +- `/cleanups/` — Public showcase of completed cleanups with before/after galleries +- `/accounts/login/` and `/accounts/signup/` — Authentication -![iPhone map overview](docs/screenshots/map-overview-iphone.png) +### District Abstraction +- District boundaries stored in DB (not hardcoded) +- Putnam County boundary rendered as outer mask; District 3 shown as labeled inner boundary +- Trash sites auto-assigned to districts via spatial query +- Designed for future multi-district support -## 1) Current MVP Capabilities +### Security +- Rate limiting on all API endpoints (django-ratelimit) +- IP ban table with optional expiry +- CSRF protection on all write endpoints +- Photo upload validation (count, size, MIME type) -- Add `TrashSite` point pins with default status `PENDING`. -- View shared trash pins and cleanup routes on a common map. -- Mark a trash site as `CLEANED` with: - - note - - bags count - - one or more photos -- Draw and save `RouteCleanup` polylines with: - - notes - - optional time spent - - optional photos - - server-computed `distance_miles` -- Filter map features by: - - trash status (`PENDING`, `IN_PROGRESS`, `CLEANED`) - - date range (`7`, `30`, `all`) -- Toggle county boundary overlays for Putnam, a six-county TN preset, the full Upper Cumberland, or a custom county mix. -- Review a basic activity feed at `/updates/`. -- Review per-user contribution totals at `/impact/`. -- Submit in-app bug/request/general feedback from the top bar. -- Enforce creator/admin-only TrashSite edits and admin-only invalidation. -- Manage all records through Django admin. +## Tech Stack -## 2) Tech Stack - -- Python 3.12 -- Django 5.2.x +- Python 3.12, Django 5.2.x - PostgreSQL 16 + PostGIS 3.4 -- Pillow for image uploads -- Gunicorn + WhiteNoise for production serving -- django-storages + boto3 for S3/R2-compatible media storage -- Leaflet + leaflet-draw via CDN - -## 3) Project Layout - -```text -putnam_trashmap/ - manage.py - requirements.txt - Dockerfile - docker-compose.yml - .env.example - .dockerignore - README.md - AGENTS.md - docker/ - web-entrypoint.sh - putnam_trashmap/ - settings.py - urls.py - geoapp/ - admin.py - models.py - permissions.py - services.py - signals.py - urls.py - views.py - tests.py - migrations/ - templates/ - base.html - geoapp/impact.html - geoapp/map.html - geoapp/updates.html - registration/login.html - static/ - js/base.js - css/site.css - css/map.css - js/map.js - render.yaml -``` - -## Documentation - -- [Feature inventory (as-is)](docs/FEATURES.md) -- [QA coverage matrix](docs/QA_MATRIX.md) -- [Manual test scripts](docs/MANUAL_TESTS.md) -- [UI roadmap (future only)](docs/UI_ROADMAP.md) - -## Theme Palette - -Edit all app theme and map colors in one place: +- Pillow, Gunicorn, WhiteNoise +- django-storages + boto3 for S3/R2 media +- django-ratelimit for API throttling +- Leaflet 1.9.4 + leaflet-draw 1.0.4 +- CARTO Voyager basemap tiles -- `static/css/palette.css` +## Data Model -This file controls: - -- top bar and button colors -- panel/surface/border colors -- modal overlay color -- map marker status colors -- route and draw-line colors - -## 4) Data Model +### `District` +- `id` UUID, `name`, `slug` (unique), `geometry` MultiPolygonField, `active` bool ### `TrashSite` - -- `id` UUID (PK) -- `status`: `PENDING | IN_PROGRESS | CLEANED | INVALID` -- `location`: `PointField(geography=True, srid=4326)` -- `title`, `description` optional -- `severity`: `LIGHT | MEDIUM | HEAVY` optional -- `hazard_flag`: bool -- `created_by` (required), `claimed_by` (optional) -- `created_at`, `updated_at`, `cleaned_at` - -### `RouteCleanup` - -- `id` UUID (PK) -- `geometry`: `LineStringField(geography=True, srid=4326)` -- `status`: `LOGGED | VERIFIED` -- `notes` optional -- `distance_miles` float (computed server-side on save) -- `time_spent_minutes` optional -- `created_by`, `created_at` +- `id` UUID, `status` (PENDING/IN_PROGRESS/CLEANED/INVALID) +- `location` PointField, `area` PolygonField (optional, for area reports) +- `district` FK(District), `title`, `description`, `severity`, `hazard_flag` +- `created_by`, `claimed_by`, `created_at`, `cleaned_at` ### `CleanupProof` - -- `id` UUID (PK) -- Links to `trash_site` or `route_cleanup` (at least one expected) -- `note`, `bags_count` -- `created_by`, `created_at` +- `trash_site` FK, `note`, `bags_count`, `created_by` ### `Photo` +- `image` ImageField, `proof` FK, `photo_type` (REPORT/BEFORE/AFTER) -- `id` UUID (PK) -- `image` (`ImageField`) -- `proof` FK -- `created_at` - -## 5) Geometry and Distance Rules - -- Coordinate order is always `[lng, lat]`. -- Spatial fields use SRID 4326 and `geography=True`. -- Route distance is computed in model `save()`: - - geodesic segment sum in meters (Haversine) - - converted with `miles = meters * 0.000621371` - -## 6) API Endpoints - -All endpoints require login. - -### HTML Routes - -- `GET /` -> redirects to `/map/` -- `GET /accounts/login/` -> Django auth login -- `GET /map/` -> main map UI -- `GET /updates/` -> recent activity feed -- `GET /impact/` -> current-user contribution totals -- `GET /admin/` -> Django admin - -### JSON Routes - -- `GET /healthz` -- `GET /api/activity/?page=1&page_size=25&days=7` -- `POST /api/feedback/` -- `GET /api/features/?bbox=minLng,minLat,maxLng,maxLat&status=PENDING,CLEANED&days=7` -- `POST /api/trash-sites/` -- `GET /api/trash-sites//` -- `PATCH /api/trash-sites//` -- `POST /api/trash-sites//mark-cleaned/` -- `GET /api/trash-sites//detail/` -- `POST /api/route-cleanups/` -- `GET /api/route-cleanups//` -- `GET /api/route-cleanups//detail/` - -### Request Examples - -Create trash site (multipart): - -```bash -curl -X POST http://127.0.0.1:8001/api/trash-sites/ \ - -H "X-CSRFToken: " \ - -b "sessionid=; csrftoken=" \ - -F "lat=36.1627" \ - -F "lng=-85.5016" \ - -F "title=Roadside litter" \ - -F "description=Near greenway entrance" \ - -F "severity=MEDIUM" \ - -F "hazard_flag=true" \ - -F "photos=@cleanup1.jpg" -``` +### `IPBan` +- `ip_address`, `reason`, `expires_at` (null = permanent) -Mark cleaned (multipart): +## API Endpoints -```bash -curl -X POST http://127.0.0.1:8001/api/trash-sites//mark-cleaned/ \ - -H "X-CSRFToken: " \ - -b "sessionid=; csrftoken=" \ - -F "note=Cleared area and bagged trash" \ - -F "bags_count=3" \ - -F "photos=@after.jpg" -``` - -Create route cleanup (JSON): - -```bash -curl -X POST http://127.0.0.1:8001/api/route-cleanups/ \ - -H "Content-Type: application/json" \ - -H "X-CSRFToken: " \ - -b "sessionid=; csrftoken=" \ - -d '{ - "coordinates": [[-85.50, 36.16], [-85.49, 36.161], [-85.485, 36.162]], - "notes": "Shoulder cleanup", - "time_spent_minutes": 45 - }' -``` +### Public (no login) +- `GET /` — Map view +- `GET /cleanups/` — Cleanup showcase +- `GET /healthz` — Health check +- `GET /api/features/?bbox=&status=&days=&district=` — GeoJSON features +- `GET /api/districts/` — Active districts with geometry +- `GET /api/trash-sites//detail/` — Site detail +- `GET /api/cleanups/?page=&page_size=` — Paginated cleaned sites -## 7) Environment Variables - -See `.env.example`. - -- `SECRET_KEY` -- `DEBUG` (`1` or `0`) -- `ALLOWED_HOSTS` (comma-separated) -- `CSRF_TRUSTED_ORIGINS` (comma-separated) -- `TIME_ZONE` -- `DATABASE_URL` (optional; overrides `POSTGRES_*`) -- `POSTGRES_DB` -- `POSTGRES_USER` -- `POSTGRES_PASSWORD` -- `POSTGRES_HOST` -- `POSTGRES_PORT` -- `DATABASE_SSL_REQUIRE` -- `CONN_MAX_AGE` -- `WEB_PORT` (host port mapped to container 8000) -- `LOG_LEVEL` -- `RUN_COLLECTSTATIC` -- `GUNICORN_WORKERS` -- `SECURE_SSL_REDIRECT` -- `SECURE_HSTS_SECONDS` -- `USE_S3_MEDIA` -- `AWS_ACCESS_KEY_ID` -- `AWS_SECRET_ACCESS_KEY` -- `AWS_STORAGE_BUCKET_NAME` -- `AWS_S3_REGION_NAME` -- `AWS_S3_ENDPOINT_URL` -- `AWS_S3_CUSTOM_DOMAIN` -- `INVITE_CODE` (reserved; signup not currently enabled) - -## 8) Setup and Run - -### Option A: Recommended (Full Docker) - -This avoids local GDAL setup. - -1. Start app stack: +### Authenticated +- `POST /api/trash-sites/` — Create report (multipart, max 5 photos) +- `PATCH /api/trash-sites//` — Update site +- `POST /api/trash-sites//mark-cleaned/` — Submit cleanup proof (before/after photos) +- `POST /api/feedback/` — Submit feedback -```powershell -cd C:\Users\Brlan\Documents\Coding\concept\site\trash-proj\putnam_trashmap -docker compose up --build -d -``` +## Setup -2. If host port 8000 is occupied, use another port: +### Docker (recommended) ```powershell -$env:WEB_PORT="8001" +cd C:\Users\Brlan\Documents\Coding\concept\site\trash-proj\putnam_trashmap docker compose up --build -d -``` - -3. Create an admin user: - -```powershell docker compose exec web python manage.py createsuperuser ``` -4. Open app: - -- `http://127.0.0.1:8000/map/` (or `8001` if `WEB_PORT=8001`) -- `http://127.0.0.1:8000/accounts/login/` -- `http://127.0.0.1:8000/admin/` +App at `http://127.0.0.1:8000/`. Putnam County and District 3 boundaries are auto-seeded on startup. -5. Useful commands: - -```powershell -docker compose logs -f web -docker compose exec web python manage.py test -docker compose exec web python manage.py makemigrations -docker compose exec web python manage.py migrate -docker compose down -``` +### Environment Variables -### Option B: Local Python + Docker DB - -Use this only if you need to run Django outside containers. - -1. Start DB only: - -```powershell -docker compose up -d db -``` +See `.env.example`. Key variables: +- `REDIS_URL` — Optional Redis for rate limit cache (defaults to in-memory) -2. Create venv and install: +## Testing ```powershell -python -m venv venv -.\venv\Scripts\Activate.ps1 -pip install -r requirements.txt -``` - -3. Export environment variables (`POSTGRES_HOST=127.0.0.1`): - -```powershell -$env:SECRET_KEY="dev-secret-change-me" -$env:DEBUG="1" -$env:ALLOWED_HOSTS="127.0.0.1,localhost" -$env:TIME_ZONE="America/Chicago" -$env:POSTGRES_DB="putnam_trashmap" -$env:POSTGRES_USER="putnam" -$env:POSTGRES_PASSWORD="putnam" -$env:POSTGRES_HOST="127.0.0.1" -$env:POSTGRES_PORT="5432" -``` - -4. Run: - -```powershell -python manage.py migrate -python manage.py createsuperuser -python manage.py runserver -``` - -## 9) Windows GDAL Setup (Local Python Mode Only) - -If you run Django locally and get `Could not find the GDAL library`: - -1. Install OSGeo4W and include `gdal`, `geos`, `proj`. -2. Set env vars (adjust GDAL DLL version name): - -```powershell -$env:OSGEO4W_ROOT="C:\OSGeo4W" -$env:PATH="$env:OSGEO4W_ROOT\bin;$env:PATH" -$env:GDAL_DATA="$env:OSGEO4W_ROOT\share\gdal" -$env:PROJ_LIB="$env:OSGEO4W_ROOT\share\proj" -$env:GDAL_LIBRARY_PATH="$env:OSGEO4W_ROOT\bin\gdal311.dll" -$env:GEOS_LIBRARY_PATH="$env:OSGEO4W_ROOT\bin\geos_c.dll" -``` - -3. Validate: - -```powershell -python -c "from django.contrib.gis import geos; print(geos.GEOSGeometry('POINT (0 0)'))" -``` - -## 10) Testing - -Current tests cover: - -- `RouteCleanup.distance_miles` computation -- `mark-cleaned` behavior (`status=CLEANED`, `cleaned_at` set, proof created) -- auth gating -- TrashSite lifecycle and bbox filters -- route create/detail/list flow -- feedback submission -- activity feed logging and impact stats -- role-aware TrashSite permissions -- health endpoint - -Run tests: - -```powershell -docker compose run --rm web python manage.py test -``` - -## Smoke checks - -Backend + unit/API smoke: - -```powershell -python -m compileall geoapp putnam_trashmap docker compose run --rm web python manage.py test ``` -UI smoke (Playwright): - -```powershell -npm install -npx playwright install chromium -npm run e2e:smoke -``` - -If your app is not on port 8000, set base URL (example 8001): - -```powershell -$env:E2E_BASE_URL="http://127.0.0.1:8001" -npm run e2e:smoke -``` - -Screenshot gallery capture: - -```powershell -powershell -ExecutionPolicy Bypass -File scripts/capture-screenshots.ps1 -``` - -If port `8000` is occupied: - -```powershell -powershell -ExecutionPolicy Bypass -File scripts/capture-screenshots.ps1 -WebPort 8001 -``` - -Screenshot output is written to: - -- `artifacts/screenshots/desktop-chrome/` -- `artifacts/screenshots/desktop-wide/` -- `artifacts/screenshots/iphone-14/` -- `artifacts/screenshots/pixel-7/` - -Each run captures: - -- `map-overview.png` -- `map-trash-focus.png` -- `map-route-focus.png` -- `updates.png` -- `impact.png` - -If the app is already running and you only want to regenerate screenshots: +## Accessibility -```powershell -docker compose exec -T web python manage.py seed_screenshot_demo --reset -$env:E2E_BASE_URL="http://127.0.0.1:8000" -npm run e2e:screenshots -``` - -## 11) Enhancement Workflow - -Recommended branch-based process: - -1. Create a feature branch: - -```powershell -git checkout -b feat/ -``` - -2. Implement change in small slices: - -- Models and migration -- API endpoint behavior -- Frontend map behavior -- Tests -- Docs - -3. Run verification: - -```powershell -docker compose exec web python manage.py makemigrations -docker compose exec web python manage.py migrate -docker compose exec web python manage.py test -``` - -4. Manual smoke check: - -- login works -- map loads and fetches features -- create trash site -- mark cleaned with photo -- draw and save route - -5. Commit: - -```powershell -git add . -git commit -m "feat: " -``` - -## 12) Common Change Recipes - -### Add a new field to `TrashSite` - -1. Edit `geoapp/models.py`. -2. `docker compose exec web python manage.py makemigrations geoapp` -3. `docker compose exec web python manage.py migrate` -4. Update serializers in `geoapp/views.py`. -5. Update map UI/forms in `templates/geoapp/map.html` and `static/js/map.js`. -6. Add/adjust tests. - -### Add a new filter to map - -1. Add UI control in `templates/geoapp/map.html`. -2. Pass filter in `static/js/map.js` when calling `/api/features/`. -3. Apply filter logic in `geoapp/views.py` `features_api`. -4. Smoke test with different map extents and dates. - -### Add a new photo-backed workflow - -1. Use multipart form requests. -2. Read files via `request.FILES.getlist("photos")`. -3. Attach photos through `CleanupProof` + `Photo`. -4. Return media URLs in detail serializers. - -## 13) Troubleshooting - -### Docker builds but web container fails to bind port - -- Error: `bind: Only one usage of each socket address...` -- Fix: set another host port: - -```powershell -$env:WEB_PORT="8001" -docker compose up -d -``` - -- If you only need a one-off command while port `8000` is occupied: - -```powershell -docker compose run --rm web python manage.py test -docker compose run --rm web python manage.py migrate -``` - -### Docker Desktop stuck in "starting" - -- Check: - -```powershell -docker desktop status -``` - -- If `wslUpdateRequired=true`, run elevated: - -```powershell -wsl --update -wsl --shutdown -docker desktop restart -``` - -### 403 CSRF on API POST/PATCH - -- Ensure browser is logged in. -- Include CSRF token header and same-origin credentials in fetch requests. - -### GDAL import errors in local Python - -- Use Docker mode, or complete OSGeo4W setup in section 9. - -### pytest runner expectations - -- No automated test runner configured (pytest). -- Use Django's built-in runner: `python manage.py test`. - -## 14) Security and Access - -- No public signup in current MVP. -- Admin creates users in Django admin. -- All map/API endpoints require authenticated session. -- Media files are served directly by Django only in `DEBUG=1`. - -## 15) Next Logical Improvements - -- Activity timeline feed -- Better ownership/permission rules -- Per-user contribution analytics -- GPX import pipeline -- S3/R2 media storage for production -- Pagination and clustering at larger scale - -## 15.1) Private Beta / Production Path - -Recommended first production stack for a trusted-friends beta: - -- Render web service using `render.yaml` -- Render Postgres with PostGIS enabled -- Cloudflare R2 for production media uploads - -Production checklist: - -1. Set `DEBUG=0`, `ALLOWED_HOSTS`, and `CSRF_TRUSTED_ORIGINS`. -2. Configure `DATABASE_URL` from your hosted Postgres. -3. Configure R2 and set `USE_S3_MEDIA=1`. -4. Run: - -```powershell -docker compose run --rm web python manage.py migrate -docker compose run --rm web python manage.py check --deploy -``` - -5. Create a superuser and a small set of invited accounts. -6. Verify: - - login - - map load - - trash report - - mark cleaned with photo - - route logging - - feedback submission - - `/healthz` - -## 16) GitHub Repo Setup - -This project is now ready to publish with: - -- `.gitignore` for Python/Django/Node local artifacts -- `.env.example` template (without secrets) -- GitHub Actions workflow at `.github/workflows/ci.yml` to run migrations + tests - -Initial publish commands: - -```powershell -cd C:\Users\Brlan\Documents\Coding\concept\site\trash-proj\putnam_trashmap -git init -b main -git add . -git commit -m "chore: initial project import" -git remote add origin https://github.com//.git -git push -u origin main -``` - -If a remote already exists: - -```powershell -git remote set-url origin https://github.com//.git -git push -u origin main -``` - -If you ever staged generated files before `.gitignore` was added, untrack them: - -```powershell -git rm -r --cached node_modules __pycache__ test-results playwright-report blob-report -git commit -m "chore: remove generated files from git tracking" -``` +- Skip-to-content link +- ARIA roles on all modals (`role="dialog"`, `aria-modal="true"`) +- Focus trap and return on modal open/close +- `aria-live` regions for toasts, detail panel, mode instructions +- WCAG 2.1 AA color contrast compliance +- Keyboard navigable map controls +- Responsive hamburger nav for mobile viewports diff --git a/docker/web-entrypoint.sh b/docker/web-entrypoint.sh index 2396941..55e6525 100644 --- a/docker/web-entrypoint.sh +++ b/docker/web-entrypoint.sh @@ -9,6 +9,16 @@ done echo "Applying migrations..." python manage.py migrate --noinput +echo "Seeding district data..." +python manage.py seed_district \ + --file static/data/putnam_county_boundary.geojson \ + --name "Putnam County" \ + --slug putnam-county || true +python manage.py seed_district \ + --file static/data/district_3_boundary.geojson \ + --name "District 3" \ + --slug district-3 || true + if [ "${RUN_COLLECTSTATIC:-1}" = "1" ]; then echo "Collecting static files..." python manage.py collectstatic --noinput diff --git a/docs/FEATURES.md b/docs/FEATURES.md index dfc8772..8d74acc 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -1,214 +1,210 @@ # Current Features (As-Is) -This file documents implemented behavior in the current codebase only. -Future ideas and design planning are tracked separately in `docs/UI_ROADMAP.md`. +This file documents implemented behavior in the current codebase only. ## Core User Flows -### F-001 Authenticated Access Control -- Django auth is enabled at `/accounts/login/`. -- Root path `/` redirects to `/map/`. -- Map, updates, impact, and private API endpoints are protected with `@login_required`. -- Logout is available in the top bar. +### F-001 Public / Authenticated Access Control +- Map (`/`) and cleanups page (`/cleanups/`) are publicly accessible. +- Public JSON endpoints: `/api/features/`, `/api/districts/`, `/api/trash-sites//detail/`, `/api/cleanups/`. +- Write endpoints require login: `POST /api/trash-sites/`, `PATCH /api/trash-sites//`, `POST mark-cleaned`, `POST feedback`. +- Signup available at `/accounts/signup/`; login at `/accounts/login/`. - Done when: - - Unauthenticated users are redirected to login for protected HTML routes and `/api/...`. - - Authenticated users can access map and API. + - Unauthenticated users can view map, features, and cleanups. + - Write operations redirect to login when unauthenticated. -### F-002 Map Initialization and Basemap UX -- `/map/` renders a full map view using Leaflet. -- Map default center is near Cookeville (`[36.1627, -85.5016]`) with zoom 12. -- OpenStreetMap tiles are loaded. -- County boundary overlays support Putnam-only, a six-county TN preset (`Smith`, `Jackson`, `Putnam`, `White`, `Van Buren`, `Cumberland`), full Upper Cumberland, and custom county selection. -- Side panel includes filters, actions, and detail area. +### F-002 Map Initialization and District Boundary +- `/` renders a full Leaflet map view. +- District boundary is loaded dynamically from `/api/districts/` API (not a static file). +- Outside-district area is masked with a semi-transparent overlay. +- Map fits bounds to the active district on load. - Done when: - - Logged-in user sees map, controls, and basemap tiles. - - Changing county overlay presets swaps the highlighted boundaries without clearing cleanup overlays. + - Public user sees map with district boundary overlay and masked exterior. -### F-003 Shared Feature Loading with BBox + Filters +### F-003 Two-Mode Map UI +- Mode switcher at top of control panel: **Report Trash** / **Cleanup Trash**. +- **Report mode**: Place Pin or Draw Area sub-modes. + - Place Pin: click map to set a point location. + - Draw Area: activate leaflet-draw polygon tool. +- **Cleanup mode**: click existing markers to view detail and mark cleaned. + - Status filter chips (Pending / In Progress / Cleaned). +- Auth gate overlay appears when unauthenticated user attempts to interact. +- Done when: + - Mode switching updates panel, ARIA states, and map interaction behavior. + - Auth gate blocks submissions for anonymous users. + +### F-004 Feature Loading with BBox + Filters - Frontend requests `GET /api/features/` using: - `bbox=minLng,minLat,maxLng,maxLat` - `status` CSV - `days` (`7`, `30`, `all`) -- Features reload on map `moveend` and `zoomend`. -- Backend returns one `FeatureCollection` with both point and line features. + - `district` slug +- Features reload on map `moveend`. +- Backend returns `FeatureCollection` with trash site point features. - Done when: - - Moving map or applying filters updates rendered points/lines. + - Moving map or applying filters updates rendered markers. -### F-004 Create TrashSite from Map -- User clicks `Report Trash`, then clicks map to set point. +### F-005 Create TrashSite (Point or Polygon) +- Report mode: click map for point, or draw polygon for area report. +- Polygon reports auto-compute centroid for the location field. - Modal form posts multipart data to `POST /api/trash-sites/`. -- Optional fields: title, description, severity, hazard flag, photos. -- New site is created with default status `PENDING`. +- Optional fields: title, description, severity, hazard flag, photos (max 5). +- District is auto-assigned via spatial query. - Done when: - - New marker appears after submit and persists on refresh. + - New marker appears after submit. Area reports show polygon overlay. -### F-005 View TrashSite Details and Proof History +### F-006 View TrashSite Details - Clicking a trash marker loads detail from `GET /api/trash-sites//detail/`. -- Detail panel shows status, severity, hazard flag, description, and proofs. -- Proof photos are displayed in detail panel. +- Detail panel shows status, severity, hazard flag, description, photos grouped by type (report/before/after), and proof history. - Done when: - - Marker click loads full detail including proof list and image thumbnails. + - Marker click loads full detail with grouped photos and proof list. -### F-006 Mark TrashSite Cleaned with Proof +### F-007 Mark TrashSite Cleaned with Before/After Photos - `Mark Cleaned` action posts to `POST /api/trash-sites//mark-cleaned/`. -- Creates `CleanupProof` with note, bag count, optional photos. +- Creates `CleanupProof` with note, bag count, before photos, and after photos. - Sets `TrashSite.status = CLEANED` and `cleaned_at = now`. +- Before/after photos are stored with separate `photo_type` values. - Done when: - - Marker color/status updates to cleaned and proof appears in details. + - Marker status updates to cleaned. Proof with before/after photos appears in detail. -### F-007 Edit TrashSite via PATCH API +### F-008 Edit TrashSite via PATCH API - `PATCH /api/trash-sites//` supports updates for: - - `status` - - `title` - - `description` - - `severity` - - `hazard_flag` + - `status`, `title`, `description`, `severity`, `hazard_flag` - Cleaning timestamp is managed based on status transitions. - Done when: - PATCH updates fields and response reflects updated values. -### F-008 Create RouteCleanup Polyline -- `Log Cleanup Route` enables leaflet-draw polyline mode. -- Drawn line coordinates are submitted to `POST /api/route-cleanups/`. -- Optional fields: notes, time_spent_minutes, photos. +### F-009 Public Cleanups Showcase +- `/cleanups/` displays completed cleanups with before/after photo galleries. +- `GET /api/cleanups/` returns paginated cleaned sites with grouped photos. +- Done when: + - Public visitors can browse completed cleanups without login. + +### F-010 Admin Management +- Django admin enabled for `District`, `TrashSite`, `CleanupProof`, `Photo`, `Profile`, `ActivityLog`, `FeedbackEntry`, `IPBan`. +- GIS admin used for spatial models. - Done when: - - Saved route appears on map after submit. + - Admin user can manage all records. -### F-009 View RouteCleanup Details -- Clicking route line loads detail from `GET /api/route-cleanups//detail/`. -- Detail panel shows notes, status, time spent, and distance in miles. +### F-011 In-App Feedback Reporting +- Authenticated users submit `BUG`, `REQUEST`, or `GENERAL` feedback from top bar modal. +- Stored in `FeedbackEntry`. - Done when: - - Route click shows detail and distance value. + - Feedback submits without leaving the app. -### F-010 Admin Management (Including Delete) -- Django admin is enabled for `TrashSite`, `RouteCleanup`, `CleanupProof`, `Photo`, `Profile`, `ActivityLog`, and `FeedbackEntry`. -- GIS admin is used for spatial models. -- Admin supports edit/search/filter and delete operations. +### F-012 Role-Aware TrashSite Permissions +- `Profile` records support `MEMBER` and `ADMIN` roles. +- TrashSite PATCH editing limited to creator or admin. +- Setting status to `INVALID` limited to admins. +- Marking cleaned available to any authenticated user unless site is `INVALID`. - Done when: - - Admin user can create/edit/delete records and map reflects deletions. + - Unauthorized edits rejected with 403. + +## District Abstraction -### F-019 Activity Updates Feed -- `/updates/` shows recent activity across reports, cleanup proofs, and route logging. -- `GET /api/activity/` returns paginated activity entries with map focus metadata. -- Activity cards link back to `/map/` with `focus_type` and `focus_id` query parameters. +### F-013 District Model and API +- `District` model stores name, slug, MultiPolygon geometry, active flag. +- `GET /api/districts/` returns active districts with GeoJSON geometry. +- Trash sites auto-assigned to districts via `geometry__covers` spatial query. +- Designed for future multi-district support. - Done when: - - Logged-in user can open `/updates/`, see recent events, and jump to the relevant map feature. + - District boundary loads from API. Sites are assigned to districts on creation. -### F-020 Personal Impact Dashboard -- `/impact/` shows contribution totals for the current user. -- Totals include: - - reported sites - - cleaned sites - - bags collected - - logged routes - - route miles - - minutes logged +## Security and Anti-Abuse + +### F-014 Rate Limiting +- `django-ratelimit` decorators on all API endpoints. +- Per-IP limits on read endpoints; per-user limits on write endpoints. +- Optional Redis backend via `REDIS_URL`; defaults to in-memory cache. - Done when: - - Logged-in user can open `/impact/` and see their contribution summary. + - Exceeding rate limits returns 403. -### F-021 In-App Feedback Reporting -- Authenticated users can submit `BUG`, `REQUEST`, or `GENERAL` feedback from the top bar modal. -- Feedback is stored in `FeedbackEntry` and managed through Django admin. -- Submitted feedback includes free-text message and current page URL. +### F-015 IP Ban Middleware +- `IPBanMiddleware` checks every request against `IPBan` table. +- Supports permanent bans (null expiry) and temporary bans with expiry. +- Uses `X-Forwarded-For` header with fallback to `REMOTE_ADDR`. - Done when: - - Logged-in user can send feedback without leaving the app and admins can review it later. + - Banned IPs receive 403. Expired bans are ignored. -### F-022 Role-Aware TrashSite Permissions -- `Profile` records support `MEMBER` and `ADMIN` roles. -- TrashSite PATCH editing is limited to creator or admin. -- Setting TrashSite status to `INVALID` is limited to admins. -- Marking a site cleaned remains available to authenticated users unless the site is already `INVALID`. +### F-016 Photo Upload Validation +- Server-side validation: max 5 files, 10 MB each, image MIME types only (JPEG, PNG, WebP, HEIC). +- Applied on report creation and cleanup proof submission. - Done when: - - Unauthorized edits are rejected with 403 JSON errors. - - Admin-only invalidation is enforced. + - Oversized, over-count, or wrong-type uploads are rejected. ## Data + Geometry Rules -### F-011 Spatial Model Standards -- Spatial fields: - - `TrashSite.location` = `PointField(geography=True, srid=4326)` - - `RouteCleanup.geometry` = `LineStringField(geography=True, srid=4326)` -- UUID primary keys are used for domain models. -- Spatial indexes are enabled on geometry fields. -- Coordinate convention is `[lng, lat]`. +### F-017 Spatial Model Standards +- `TrashSite.location` = `PointField(geography=True, srid=4326)` +- `TrashSite.area` = `PolygonField(geography=True, srid=4326)` (optional) +- `District.geometry` = `MultiPolygonField(geography=True, srid=4326)` +- UUID primary keys on domain models. +- Coordinate convention: `[lng, lat]`. - Done when: - - Stored and returned geometry consistently uses SRID 4326 and `[lng, lat]`. + - All geometry uses SRID 4326 and `[lng, lat]` order. -### F-012 Server-Side Route Distance Calculation -- `RouteCleanup.save()` computes line length in meters using Haversine segment sum. -- Conversion uses `distance_miles = meters * 0.000621371`. -- Distance recalculates on save when geometry is present. +### F-018 Proof + Photo Evidence Linkage +- `CleanupProof` attaches to `TrashSite`. +- `Photo` attaches to `CleanupProof` with `photo_type` (REPORT/BEFORE/AFTER). - Done when: - - Persisted routes have non-zero `distance_miles` for valid multi-point lines. + - Photos are grouped by type in detail responses. -### F-013 Proof + Photo Evidence Linkage -- `CleanupProof` attaches to either `TrashSite` or `RouteCleanup`. -- `Photo` attaches to `CleanupProof`. -- Upload storage uses local `MEDIA_ROOT`; URLs exposed via serializers. +## Accessibility + +### F-019 Accessibility Features +- Skip-to-content link. +- ARIA roles on all modals (`role="dialog"`, `aria-modal="true"`). +- Focus trap and return on modal open/close. +- `aria-live` regions for toasts, detail panel, and mode instructions. +- WCAG 2.1 AA color contrast compliance. +- Keyboard navigable map controls. - Done when: - - Uploaded images are retrievable and visible in detail views. + - Screen reader announces mode changes, submissions, and errors. + - Tab navigation works within modals without escaping. ## API -### F-014 Authenticated API Surface -- Implemented JSON routes: +### F-020 API Surface +- Public endpoints: - `GET /healthz` - - `GET /api/features/` - - `GET /api/activity/` - - `POST /api/feedback/` - - `POST /api/trash-sites/` - - `GET|PATCH /api/trash-sites//` + - `GET /api/features/?bbox=&status=&days=&district=` + - `GET /api/districts/` - `GET /api/trash-sites//detail/` + - `GET /api/cleanups/?page=&page_size=` +- Authenticated endpoints: + - `POST /api/trash-sites/` + - `PATCH /api/trash-sites//` - `POST /api/trash-sites//mark-cleaned/` - - `POST /api/route-cleanups/` - - `GET /api/route-cleanups//` - - `GET /api/route-cleanups//detail/` -- Combined feature response includes `properties.type` (`trash_site` or `route_cleanup`). + - `POST /api/feedback/` - Done when: - - Endpoints return expected payloads for authenticated user sessions. + - All endpoints return expected JSON shapes. -### F-015 API Validation and Error Payloads -- Invalid inputs return JSON errors (e.g., invalid severity, bad coordinates, invalid status). -- Route creation validates minimum coordinate count and GeoJSON line type. +### F-021 API Validation and Error Payloads +- Invalid inputs return JSON errors (invalid severity, bad coordinates, invalid status). +- Photo validation enforced on upload endpoints. - Done when: - - Invalid requests return non-2xx with an `{"error": ...}` payload. + - Invalid requests return non-2xx with `{"error": ...}`. ## Dev/Ops -### F-016 Dockerized Development Stack -- `docker-compose.yml` includes: - - `db` (`postgis/postgis:16-3.4`) - - `web` (Django app image with GDAL/GEOS/PROJ installed) -- Entry point waits for DB, runs migrations, optionally collects static files, then starts: - - Django `runserver` in debug - - Gunicorn when `DEBUG=0` -- Host web port is configurable with `WEB_PORT`. -- Done when: - - `docker compose up --build -d` boots healthy DB + running web app. - -### F-017 Local Python Runtime + Config -- Settings are environment-driven for DB/auth/static/media/security/storage. -- Local media served in DEBUG mode. -- Object storage is supported via S3-compatible settings for production media. -- Database config supports `DATABASE_URL` as well as individual `POSTGRES_*` variables. -- Django test suite exists via `python manage.py test`. -- No pytest configuration is present in the repo. +### F-022 Dockerized Development Stack +- `docker-compose.yml`: `db` (PostGIS 16) + `web` (Django with GDAL/GEOS/PROJ). +- Entrypoint waits for DB, runs migrations, seeds district data, collects static, starts server. - Done when: - - Local run works with env vars and `manage.py test` executes. + - `docker compose up --build -d` boots healthy stack with seeded district. -### F-023 Visual Screenshot Capture Workflow -- Playwright can capture a repeatable screenshot gallery for desktop and mobile form factors. -- `python manage.py seed_screenshot_demo` creates deterministic demo records and a dedicated screenshot user. -- `scripts/capture-screenshots.ps1` starts the Docker stack, runs migrations, seeds demo data, and writes screenshots to `artifacts/screenshots/`. +### F-023 Signup Flow +- `/accounts/signup/` with Django `UserCreationForm`. +- Auto-login after successful registration, redirect to map. +- Authenticated users redirected away from signup page. - Done when: - - One command produces current screenshots for desktop and phone-sized layouts without manual browser resizing. + - New users can register and immediately use the app. -## Observability / Troubleshooting +## Observability -### F-018 Runtime Diagnostics and Error Visibility -- Container startup logs show DB wait, migration run, static collection, and server start. -- API errors are explicit JSON (`{"error": ...}`). -- Frontend surfaces request failures in alert dialogs or detail panel text. -- `GET /healthz` reports basic application/database readiness. +### F-024 Runtime Diagnostics +- `GET /healthz` reports application/database readiness. +- API errors return explicit JSON. +- Frontend uses toast notifications for success/error feedback. - Done when: - - Failures are visible to developers/users without attaching a debugger. + - Failures visible without debugger. diff --git a/docs/MANUAL_TESTS.md b/docs/MANUAL_TESTS.md index efaf77f..dbff191 100644 --- a/docs/MANUAL_TESTS.md +++ b/docs/MANUAL_TESTS.md @@ -1,307 +1,269 @@ # Manual Test Scripts -Repeatable manual scripts mapped 1:1 to `docs/QA_MATRIX.md`. +Repeatable manual scripts mapped to `docs/QA_MATRIX.md`. ## Preconditions - Test environment has running PostGIS and Django app. -- At least one valid login account exists. -- For API tests, you are logged in through browser session or have session+CSRF cookies. +- District 3 boundary is seeded (automatic on Docker startup). +- For write-endpoint tests, you have a logged-in browser session. -## MT-001 Authentication Required for Map and API +## MT-001 Public Map Access Related feature: `F-001` Steps: 1. Open a private/incognito browser window. -2. Visit `/map/`. -3. Visit `/api/features/` directly. -4. Log in at `/accounts/login/`, then revisit `/map/`. +2. Visit `/`. +3. Visit `/cleanups/`. +4. Visit `/api/features/`. +5. Visit `/api/districts/`. Expected: -- Unauthenticated requests redirect to login. -- Authenticated user can access map and API. +- All pages load without login. +- Map shows district boundary and any existing features. -## MT-002 Map Loads and Basemap UI +## MT-002 Auth Gate on Write Endpoints +Related feature: `F-001` + +Steps: +1. Without logging in, click "Report Trash" on the map. +2. Try to interact (place pin or draw area). +3. Verify auth gate overlay appears. +4. Log in at `/accounts/login/`. +5. Retry the report action. + +Expected: +- Unauthenticated users see auth gate overlay. +- After login, report submission works. + +## MT-003 Map Initialization and District Boundary Related feature: `F-002` Steps: -1. Log in. -2. Open `/map/`. -3. Confirm map container, side panel, and buttons render. -4. Switch the county overlay to the six-county TN preset. -5. Pan/zoom and verify map tiles load. +1. Open `/`. +2. Confirm map loads with district boundary polygon. +3. Verify area outside district is masked/dimmed. +4. Confirm map fits to district bounds. Expected: -- Map opens near Putnam/Cookeville area. -- Side controls and basemap are visible and usable. -- The six-county overlay highlights `Smith`, `Jackson`, `Putnam`, `White`, `Van Buren`, and `Cumberland`. -- Changing overlay presets keeps the cleanup features in place. +- District boundary is visible and loaded from API. +- Outside area has semi-transparent overlay. -## MT-003 Browse/List Features with Filters +## MT-004 Two-Mode Switching Related feature: `F-003` Steps: -1. Ensure at least one trash site and one route exist. -2. On `/map/`, toggle status checkboxes and click `Apply Filters`. -3. Switch date range between `7`, `30`, and `all`. -4. Pan to a different area and back. +1. Open the map. +2. Click "Report Trash" mode button. +3. Verify report panel shows Place Pin / Draw Area buttons. +4. Click "Cleanup Trash" mode button. +5. Verify cleanup panel shows status filter chips. Expected: -- Feature set updates after filter changes and map movement. -- Hidden statuses are removed from current map rendering. +- Mode buttons toggle `aria-pressed` state. +- Panel content switches between report and cleanup modes. +- Screen reader announces mode change. -## MT-004 Create TrashSite Item -Related feature: `F-004` +## MT-005 Report Trash - Place Pin +Related feature: `F-005` Steps: -1. Click `Report Trash`. -2. Click a point on the map. -3. Fill optional fields (title/description/severity/hazard). -4. Attach one image and submit. +1. Log in and select Report Trash mode. +2. Click "Place Pin", then click on the map. +3. Fill title, description, severity, hazard flag. +4. Attach 1-3 photos and submit. Expected: -- New marker appears. -- Opening details shows entered fields and uploaded photo. +- New marker appears on map. +- Toast notification confirms submission. +- Detail shows entered fields and uploaded photos. -## MT-005 View TrashSite Detail and Proofs +## MT-006 Report Trash - Draw Area Related feature: `F-005` Steps: -1. Click an existing trash marker. -2. Confirm detail panel values (status/severity/hazard/description). -3. Confirm proof history section and images render. +1. Select Report Trash mode. +2. Click "Draw Area" and draw a polygon on the map. +3. Complete the polygon and fill the report form. +4. Submit. Expected: -- Detail panel populates from API and includes proof content. +- Polygon overlay and centroid marker appear. +- Detail shows area geometry in response. -## MT-006 Mark TrashSite Cleaned +## MT-007 View TrashSite Detail Related feature: `F-006` Steps: -1. Open a `PENDING` trash site detail. -2. Click `Mark Cleaned`. -3. Enter note + bags_count, attach photo, submit. -4. Reopen same marker detail. +1. Click an existing trash marker. +2. Confirm detail panel shows status, severity, description. +3. Confirm photos are grouped into report/before/after sections. +4. Confirm proof history is listed. Expected: -- Status changes to `CLEANED`. -- Cleanup proof appears with note, bags_count, and photo. +- Detail populates from API with grouped photo display. -## MT-007 Edit TrashSite via API (PATCH) +## MT-008 Mark TrashSite Cleaned Related feature: `F-007` Steps: -1. Capture a trash site ID from detail payload (or admin URL). -2. Send PATCH to `/api/trash-sites//` with updated fields (for example status/title). -3. Reload map and open that marker. +1. In Cleanup mode, click a PENDING trash site marker. +2. Click "Mark Cleaned". +3. Upload before photo(s) and after photo(s). +4. Enter note and bags count, submit. +5. Recheck the marker. Expected: -- API returns updated values. -- Marker detail reflects updated fields. +- Status changes to CLEANED. +- Before/after photos appear in detail grouped correctly. +- Site appears on `/cleanups/` page. -## MT-008 Create RouteCleanup Item +## MT-009 Edit TrashSite via PATCH Related feature: `F-008` Steps: -1. Click `Log Cleanup Route`. -2. Draw a polyline with at least 2 points. -3. Fill notes/time and submit. +1. Get a trash site ID from detail panel. +2. Send PATCH to `/api/trash-sites//` with updated fields. +3. Reload map and check that marker detail reflects changes. Expected: -- New route line appears on map. -- Route persists after refresh. +- API returns updated values. +- Status transitions manage `cleaned_at` correctly. -## MT-009 Route Detail and Distance Display +## MT-010 Public Cleanups Page Related feature: `F-009` Steps: -1. Click a route line. -2. Verify detail panel shows notes, time, and distance. -3. Compare popup distance summary to detail distance. +1. Open `/cleanups/` without logging in. +2. Verify cleanup cards show title, severity, description, before/after photos. +3. If more than 12 cleanups exist, verify pagination works. Expected: -- Distance is displayed and greater than zero for valid non-trivial route. +- Page is publicly accessible. +- Before/after photo galleries render correctly. -## MT-010 Admin CRUD and Delete Item -Related feature: `F-010` +## MT-011 Feature Filters +Related feature: `F-004` Steps: -1. Log in as superuser at `/admin/`. -2. Open TrashSite or RouteCleanup list. -3. Edit a record and save. -4. Delete a record via admin. -5. Return to `/map/` and refresh features. +1. On the map, apply status filter (e.g., CLEANED only). +2. Switch date range between 7 days, 30 days, all. +3. Pan to a different area and back. Expected: -- Record updates are saved. -- Deleted record no longer appears on map. +- Feature set updates after filter changes and map movement. +- Only matching features are displayed. -## MT-011 Geometry Convention and SRID Sanity Check -Related feature: `F-011` +## MT-012 Signup Flow +Related feature: `F-023` Steps: -1. Create one trash site and one route. -2. Open detail JSON endpoints. -3. Verify coordinate order is `[lng, lat]` in returned payloads. -4. In admin GIS map, verify locations render in expected area (Putnam County vicinity). +1. Visit `/accounts/signup/`. +2. Create a new account. +3. Verify redirect to map and auto-login. +4. While logged in, visit `/accounts/signup/` again. Expected: -- Coordinates are consistently `[lng, lat]`. -- Geometry renders in the expected map region. +- New account is created and user is logged in. +- Already-authenticated users are redirected to `/`. -## MT-012 Route Distance Calculation Validation +## MT-013 Role-Aware Permissions Related feature: `F-012` Steps: -1. Draw a short route (2-3 points). -2. Save route and read `distance_miles` in route detail. -3. Draw a noticeably longer route and save. -4. Compare distances. - -Expected: -- Longer route has larger `distance_miles`. -- Distances are non-zero for valid lines. - -## MT-013 Proof/Photo Upload and Retrieval -Related feature: `F-013` - -Steps: -1. Upload photo while creating a trash site or marking cleaned. -2. Open detail panel and verify image is shown. -3. Open image URL in new browser tab. +1. Create a trash site as User A. +2. Log in as User B and try PATCH on User A's site. +3. Log in as User A and try setting status to INVALID. +4. Log in as admin user and set status to INVALID. Expected: -- Image URL resolves and image loads. -- Proof references are visible in detail payload/UI. +- Non-owner gets 403 on PATCH. +- Non-admin gets 403 on invalidation. +- Admin can invalidate. -## MT-014 API Surface Smoke Test -Related feature: `F-014` +## MT-014 Feedback Submission +Related feature: `F-011` Steps: -1. Hit each implemented endpoint with authenticated session: - - `/api/features/` - - `/api/trash-sites//` - - `/api/trash-sites//detail/` - - `/api/route-cleanups//detail/` -2. Validate status code and minimal shape of response. +1. Log in and click "Feedback" in the top bar. +2. Submit a BUG type feedback with message. +3. Check admin for the created entry. Expected: -- Endpoints return 2xx for valid authenticated requests. -- Combined features payload includes both `trash_site` and `route_cleanup` types (when data exists). +- Feedback modal submits successfully. +- Entry stored with type, message, user, and page URL. -## MT-015 Geometry and Payload Validation Errors +## MT-015 IP Ban Enforcement Related feature: `F-015` Steps: -1. POST `/api/route-cleanups/` with invalid coordinates (`[]` or one point). -2. POST `/api/trash-sites/` with invalid severity. -3. PATCH trash site with invalid status. +1. Add an IP ban via Django admin. +2. Attempt to access any page from that IP. +3. Set an expiry in the past and retry. Expected: -- Server returns non-2xx with JSON body containing `error`. +- Active ban returns 403 JSON response. +- Expired ban allows access. -## MT-016 Docker Up Workflow +## MT-016 Photo Upload Limits Related feature: `F-016` Steps: -1. Run `docker compose up --build -d`. -2. Check `docker ps`. -3. Check web logs. -4. Open app in browser. - -Expected: -- `db` is healthy and `web` is running. -- Web logs show migrations and server startup. - -## MT-017 Local Python Run Workflow -Related feature: `F-017` - -Steps: -1. Start DB only: `docker compose up -d db`. -2. Activate venv and install requirements. -3. Set env vars from `.env.example`. -4. Run `python manage.py migrate` then `python manage.py runserver`. -5. Run `python manage.py test`. - -Expected: -- Local server starts and app is reachable. -- Django test suite runs with no import/runtime errors. - -## MT-023 Visual Screenshot Capture Workflow -Related feature: `F-023` - -Steps: -1. Run `powershell -ExecutionPolicy Bypass -File scripts/capture-screenshots.ps1`. -2. Wait for the script to finish Docker startup, migration, seed, and Playwright capture. -3. Open `artifacts/screenshots/desktop-wide/` and `artifacts/screenshots/iphone-14/`. -4. Verify `map-overview.png`, `map-trash-focus.png`, `map-route-focus.png`, `updates.png`, and `impact.png` exist. +1. Try uploading 6 photos in a report form. +2. Try uploading a file over 10 MB. +3. Try uploading a non-image file (e.g., PDF). Expected: -- Screenshot folders are created for desktop and phone profiles. -- Images show logged-in app pages without obvious broken rendering or blank map output. +- Server rejects with appropriate error message. -## MT-018 Diagnostics and Error Visibility -Related feature: `F-018` +## MT-017 Docker Startup +Related feature: `F-022` Steps: -1. Trigger a known API validation error (for example MT-015 step 1). -2. In browser, attempt invalid action and observe UI feedback. -3. Inspect `docker compose logs web`. +1. Run `docker compose up --build -d`. +2. Check `docker ps` for healthy db + running web. +3. Check web logs for migrations, district seed, and server start. +4. Open app in browser. Expected: -- API returns readable JSON error. -- UI shows failure signal (alert or detail text). -- Logs capture request/response context for debugging. +- Stack boots cleanly with district data auto-seeded. -## MT-019 Activity Updates Feed +## MT-018 Accessibility Checks Related feature: `F-019` Steps: -1. Create a trash site. -2. Mark a site cleaned. -3. Log a cleanup route. -4. Open `/updates/`. -5. Click an `Open on map` link from one activity card. +1. Tab through the page and verify skip-to-content link works. +2. Open a modal (report or feedback) and verify focus is trapped. +3. Press Escape to close modal and verify focus returns. +4. Use a screen reader to verify mode change announcements. +5. Check color contrast with a browser dev tools audit. Expected: -- Updates page shows recent events in newest-first order. -- Clicking an entry opens `/map/` and focuses the related site or route. +- Focus management works correctly in all modals. +- ARIA live regions announce dynamic content changes. +- Colors meet WCAG 2.1 AA contrast requirements. -## MT-020 Personal Impact Dashboard +## MT-019 API Surface Smoke Test Related feature: `F-020` Steps: -1. Log in as a user with at least one report, cleanup proof, and route. -2. Open `/impact/`. -3. Compare displayed totals to known records in admin or recent actions. - -Expected: -- Impact page shows totals for reports, cleanups, bags, routes, miles, and minutes. -- Numbers are plausible for the current user only. - -## MT-021 In-App Feedback Submission -Related feature: `F-021` - -Steps: -1. Log in and open any authenticated page. -2. Click `Report Issue / Request` in the top bar. -3. Submit one `BUG` and one `REQUEST`. -4. Open `/admin/` and inspect `Feedback entries`. +1. Hit each endpoint and verify response shape: + - `GET /api/features/` - FeatureCollection with features array + - `GET /api/districts/` - districts array with geometry + - `GET /api/trash-sites//detail/` - site with photos/proofs/permissions + - `GET /api/cleanups/` - paginated results with count/page/num_pages + - `GET /healthz` - ok + database status Expected: -- Feedback modal submits successfully. -- Each entry stores type, message, user, and current page URL. -- Admin can review feedback later. +- All return 200 with documented JSON shapes. -## MT-022 Role-Aware TrashSite Permissions -Related feature: `F-022` +## MT-020 Mobile Viewport +Related feature: `F-003` Steps: -1. Create a trash site as User A. -2. Log in as User B and try to edit that site through the PATCH API. -3. Log in as User A and try to set the site to `INVALID`. -4. Log in as an admin-role user and set the site to `INVALID`. +1. Open map at 375px width (or phone device emulation). +2. Test mode switching, report submission, and cleanup flow. +3. Verify modals are usable on small screens. Expected: -- User B receives a 403 when editing User A's site. -- User A receives a 403 when attempting invalidation. -- Admin-role user can invalidate the site successfully. +- All interactions work on mobile viewport. +- Modals are scrollable and dismissible. diff --git a/docs/QA_MATRIX.md b/docs/QA_MATRIX.md index 19dea82..b48e4e3 100644 --- a/docs/QA_MATRIX.md +++ b/docs/QA_MATRIX.md @@ -1,30 +1,31 @@ # QA Matrix -Coverage inventory for implemented features. +Coverage inventory for implemented features. Feature IDs are sourced from `docs/FEATURES.md`. -| ID | Feature | Scope / Acceptance Criteria | Auto Tests (✅/⚠️/❌) | Manual Tests (✅/⚠️/❌) | Test Locations | Manual Script | Notes / Feedback | Owner | -|---|---|---|---|---|---|---|---|---| -| F-001 | Authenticated access control | Unauthenticated users are redirected for `/map/` and `/api/*`; authenticated users can access. | ✅ | ⚠️ | `geoapp/tests.py::AuthGateTests` | [MT-001](MANUAL_TESTS.md#mt-001-authentication-required-for-map-and-api) | Covers unauth redirects and authenticated success paths. | TBD | -| F-002 | Map initialization and basemap UX | `/map/` renders controls + tiles centered near Cookeville. | ✅ | ⚠️ | `e2e/map-smoke.spec.js`, `e2e/screenshot-gallery.spec.js` | [MT-002](MANUAL_TESTS.md#mt-002-map-loads-and-basemap-ui) | Smoke covers map load and the six-county overlay preset toggle; screenshot suite still covers responsive rendering. | TBD | -| F-003 | Shared feature loading with bbox + filters | Status/date/bbox filtering updates rendered features on move/zoom and apply. | ✅ | ⚠️ | `geoapp/tests.py::FeaturesFilterApiTests` | [MT-003](MANUAL_TESTS.md#mt-003-browselist-features-with-filters) | Includes bbox + status + days filtering assertions. | TBD | -| F-004 | Create TrashSite from map | Multipart create persists marker with optional metadata/photos. | ✅ | ⚠️ | `geoapp/tests.py::TrashSiteApiLifecycleTests::test_create_trash_site_returns_expected_fields_and_geometry` | [MT-004](MANUAL_TESTS.md#mt-004-create-trashsite-item) | Verifies 201 response, status, coordinates, and SRID persistence. | TBD | -| F-005 | TrashSite detail + proof history | Detail panel loads serialized fields and proof/photo list. | ❌ | ⚠️ | N/A | [MT-005](MANUAL_TESTS.md#mt-005-view-trashsite-detail-and-proofs) | Missing detail endpoint tests. | TBD | -| F-006 | Mark TrashSite cleaned | Marks status `CLEANED`, sets `cleaned_at`, stores proof and bags count. | ✅ | ⚠️ | `geoapp/tests.py::MarkCleanedApiTests::test_mark_cleaned_sets_status_and_cleaned_at` | [MT-006](MANUAL_TESTS.md#mt-006-mark-trashsite-cleaned) | Photo upload branch still untested automatically. | TBD | -| F-007 | Edit TrashSite via PATCH | PATCH updates supported fields; cleaning timestamp toggles by status. | ✅ | ⚠️ | `geoapp/tests.py::TrashSiteApiLifecycleTests::test_patch_trash_site_updates_fields_and_cleaned_at_transitions` | [MT-007](MANUAL_TESTS.md#mt-007-edit-trashsite-via-api-patch) | Includes status toggles and `cleaned_at` transition assertions. | TBD | -| F-008 | Create RouteCleanup polyline | Drawn line saves with notes/time/photos and appears on map. | ✅ | ⚠️ | `geoapp/tests.py::RouteCleanupApiTests::test_create_route_get_detail_and_list_in_features_bbox` | [MT-008](MANUAL_TESTS.md#mt-008-create-routecleanup-item) | Validates route create API and feature-list presence. | TBD | -| F-009 | Route detail and distance display | Route detail endpoint and panel show distance/notes/time. | ✅ | ⚠️ | `geoapp/tests.py::RouteCleanupApiTests::test_create_route_get_detail_and_list_in_features_bbox` | [MT-009](MANUAL_TESTS.md#mt-009-route-detail-and-distance-display) | Confirms detail endpoint returns non-zero `distance_miles`. | TBD | -| F-010 | Admin management incl. delete | Admin can edit/delete model records and deletions stop showing on map. | ❌ | ⚠️ | N/A | [MT-010](MANUAL_TESTS.md#mt-010-admin-crud-and-delete-item) | Delete only available via admin, not map UI/API. | TBD | -| F-019 | Activity updates feed | `/updates/` and `/api/activity/` show recent events and link back to focused map features. | ✅ | ⚠️ | `geoapp/tests.py::ActivityAndImpactTests` | [MT-019](MANUAL_TESTS.md#mt-019-activity-updates-feed) | API logging and feed payload are covered; HTML page still needs manual smoke. | TBD | -| F-020 | Personal impact dashboard | `/impact/` shows current-user totals for reports, cleanups, bags, routes, miles, and time. | ✅ | ⚠️ | `geoapp/tests.py::ActivityAndImpactTests` | [MT-020](MANUAL_TESTS.md#mt-020-personal-impact-dashboard) | Stats builder is exercised through activity/impact tests; page render is still manual. | TBD | -| F-021 | In-app feedback reporting | Authenticated users can submit bug/request/general feedback with page URL context. | ✅ | ⚠️ | `geoapp/tests.py::HealthAndFeedbackTests::test_feedback_submission_creates_entry` | [MT-021](MANUAL_TESTS.md#mt-021-in-app-feedback-submission) | Submission flow is covered; admin triage remains manual. | TBD | -| F-022 | Role-aware TrashSite permissions | Creator/admin-only PATCH editing and admin-only invalidation are enforced. | ✅ | ⚠️ | `geoapp/tests.py::PermissionRoleTests` | [MT-022](MANUAL_TESTS.md#mt-022-role-aware-trashsite-permissions) | Current enforcement covers TrashSite edits/invalidation only. Route moderation is still future work. | TBD | -| F-011 | Spatial model standards | UUIDs + `geography=True` + `srid=4326` + `[lng,lat]` convention. | ❌ | ⚠️ | N/A | [MT-011](MANUAL_TESTS.md#mt-011-geometry-convention-and-srid-sanity-check) | No automated schema/serialization convention test yet. | TBD | -| F-012 | Server-side route distance computation | `distance_miles` persists non-zero for valid lines and scales with geometry length. | ✅ | ⚠️ | `geoapp/tests.py::RouteCleanupModelTests::test_distance_miles_is_computed_from_geometry` | [MT-012](MANUAL_TESTS.md#mt-012-route-distance-calculation-validation) | Good baseline; add multi-segment edge-case tests later. | TBD | -| F-013 | Proof + photo evidence linkage | Proof and image references are returned and media URLs are accessible in DEBUG. | ❌ | ⚠️ | N/A | [MT-013](MANUAL_TESTS.md#mt-013-proofphoto-upload-and-retrieval) | Missing automated upload/media tests. | TBD | -| F-014 | Authenticated API surface | All documented API endpoints return expected JSON shapes for logged-in users. | ✅ | ⚠️ | `geoapp/tests.py::ApiSurfaceContractTests` | [MT-014](MANUAL_TESTS.md#mt-014-api-surface-smoke-test) | Contract-shape coverage added for core GET endpoints. | TBD | -| F-015 | API validation and error payloads | Invalid payloads return non-2xx with `{"error": ...}`. | ✅ | ⚠️ | `geoapp/tests.py::ApiValidationTests` | [MT-015](MANUAL_TESTS.md#mt-015-geometry-and-payload-validation-errors) | Covers invalid geometry and invalid status payload error contract. | TBD | -| F-016 | Dockerized development stack | `docker compose up --build -d` yields healthy db + running web service. | ❌ | ⚠️ | N/A | [MT-016](MANUAL_TESTS.md#mt-016-docker-up-workflow) | Startup behavior currently validated manually only. | TBD | -| F-017 | Local Python run + env config | Local server/test run works with env vars and PostGIS. | ⚠️ | ⚠️ | `geoapp/tests.py` (via `python manage.py test`) | [MT-017](MANUAL_TESTS.md#mt-017-local-python-run-workflow) | No automated test runner configured (pytest). Use Django test runner (`python manage.py test`). | TBD | -| F-023 | Visual screenshot capture workflow | One command seeds demo data and exports desktop/mobile screenshots under `artifacts/screenshots/`. | ✅ | ⚠️ | `e2e/screenshot-gallery.spec.js`, `geoapp/management/commands/seed_screenshot_demo.py`, `scripts/capture-screenshots.ps1` | [MT-023](MANUAL_TESTS.md#mt-023-visual-screenshot-capture-workflow) | Uses a dedicated screenshot user and deterministic seed data to keep captures stable. | TBD | -| F-018 | Runtime diagnostics and troubleshooting visibility | Logs + API/UI errors provide actionable debugging signals. | ✅ | ⚠️ | `geoapp/tests.py::HealthAndFeedbackTests::test_healthz_is_public_and_reports_ok` | [MT-018](MANUAL_TESTS.md#mt-018-diagnostics-and-error-visibility) | Health endpoint is automated; structured logs still need manual/operator review. | TBD | +| ID | Feature | Auto Tests | Manual Tests | Test Locations | Notes | +|---|---|---|---|---|---| +| F-001 | Public / authenticated access control | ✅ | ⚠️ | `tests.py::PublicAccessTests`, `tests.py::AuthGateTests` | Map and read APIs are public; write endpoints require login. | +| F-002 | Map initialization and district boundary | ⚠️ | ⚠️ | `e2e/map-smoke.spec.js` | Smoke covers map load; district boundary from API needs manual check. | +| F-003 | Two-mode map UI | ❌ | ⚠️ | N/A | Frontend mode switching is manual-only. | +| F-004 | Feature loading with bbox + filters | ✅ | ⚠️ | `tests.py::FeaturesFilterApiTests` | Covers bbox, status, days, and district filters. | +| F-005 | Create TrashSite (point or polygon) | ✅ | ⚠️ | `tests.py::TrashSiteApiLifecycleTests`, `tests.py::TrashSitePolygonTests` | Point, GeoJSON point, polygon creation, and district auto-assignment. | +| F-006 | View TrashSite details | ✅ | ⚠️ | `tests.py::TrashSiteApiLifecycleTests`, `tests.py::ApiSurfaceContractTests` | Detail API shape and public access verified. | +| F-007 | Mark TrashSite cleaned with before/after photos | ✅ | ⚠️ | `tests.py::MarkCleanedApiTests` | Covers status change, proof creation, and photo type separation. | +| F-008 | Edit TrashSite via PATCH | ✅ | ⚠️ | `tests.py::TrashSiteApiLifecycleTests` | Status transitions and cleaned_at management. | +| F-009 | Public cleanups showcase | ✅ | ⚠️ | `tests.py::CleanupsPageTests` | Page render, API filtering, and pagination. | +| F-010 | Admin management | ❌ | ⚠️ | N/A | Admin CRUD is manual-only. | +| F-011 | In-app feedback | ✅ | ⚠️ | `tests.py::FeedbackTests` | Submission and validation covered. | +| F-012 | Role-aware permissions | ✅ | ⚠️ | `tests.py::PermissionRoleTests` | Non-owner edit, non-admin invalidation, admin invalidation. | +| F-013 | District model and API | ✅ | ⚠️ | `tests.py::DistrictModelTests`, `tests.py::DistrictApiTests` | Creation, spatial assignment, active filtering, API shape. | +| F-014 | Rate limiting | ⚠️ | ⚠️ | N/A | Rate limits applied via decorators; most tests run with `RATELIMIT_ENABLE=False`. | +| F-015 | IP ban middleware | ✅ | ⚠️ | `tests.py::IPBanMiddlewareTests` | Permanent ban, expired ban, future expiry, XFF header. | +| F-016 | Photo upload validation | ✅ | ⚠️ | `tests.py::PhotoUploadValidationTests` | Count, size, and MIME type enforcement. | +| F-017 | Spatial model standards | ⚠️ | ⚠️ | Implicit in lifecycle tests | SRID and coordinate order verified through API responses. | +| F-018 | Proof + photo linkage | ✅ | ⚠️ | `tests.py::MarkCleanedApiTests`, `tests.py::ApiSurfaceContractTests` | Photo type grouping in detail response. | +| F-019 | Accessibility features | ❌ | ⚠️ | N/A | ARIA attributes in templates; manual/screen reader testing required. | +| F-020 | API surface | ✅ | ⚠️ | `tests.py::ApiSurfaceContractTests` | Shape validation for features, detail, cleanups, districts APIs. | +| F-021 | API validation and errors | ✅ | ⚠️ | `tests.py::ApiValidationTests` | Invalid status, severity, coordinates, and GeoJSON type. | +| F-022 | Dockerized dev stack | ❌ | ⚠️ | N/A | Manual Docker workflow only. | +| F-023 | Signup flow | ✅ | ⚠️ | `tests.py::SignupTests` | Page render, user creation, redirect, auth guard. | +| F-024 | Runtime diagnostics | ✅ | ⚠️ | `tests.py::PublicAccessTests` | Healthz endpoint automated. | diff --git a/e2e/helpers/app.js b/e2e/helpers/app.js index 255e94c..87eba30 100644 --- a/e2e/helpers/app.js +++ b/e2e/helpers/app.js @@ -116,7 +116,7 @@ async function loginAndWaitForMap(page, baseUrl, username, password) { await page.fill('input[name="password"]', password); await Promise.all([ - page.waitForURL(/\/map\/?$/), + page.waitForURL(/\/$/), page.click('button[type="submit"]'), ]); @@ -125,7 +125,7 @@ async function loginAndWaitForMap(page, baseUrl, username, password) { expect(featuresResponse.status()).toBe(200); } -async function openMapPage(page, baseUrl, path = "/map/") { +async function openMapPage(page, baseUrl, path = "/") { const featuresResponsePromise = page.waitForResponse( (resp) => resp.url().includes("/api/features/") && resp.request().method() === "GET", { timeout: 20_000 } diff --git a/e2e/map-smoke.spec.js b/e2e/map-smoke.spec.js index ea8c15b..99dd9a5 100644 --- a/e2e/map-smoke.spec.js +++ b/e2e/map-smoke.spec.js @@ -17,7 +17,22 @@ function projectCredentials(projectName) { } test.describe("Map smoke", () => { - test("login and load map with features request", async ({ page }, testInfo) => { + test("public map loads with district boundary", async ({ page }) => { + const baseUrl = await resolveBaseUrl(); + const clientErrors = attachClientErrorCollectors(page); + + await page.goto(baseUrl, { waitUntil: "domcontentloaded" }); + await expect(page.locator("#map")).toBeVisible(); + await expect(page.locator("#map.leaflet-container")).toBeVisible(); + + // Mode switcher should be present + await expect(page.locator("#btn-mode-report")).toBeVisible(); + await expect(page.locator("#btn-mode-cleanup")).toBeVisible(); + + assertNoClientErrors(clientErrors); + }); + + test("login and access report mode", async ({ page }, testInfo) => { const credentials = projectCredentials(testInfo.project.name); const demo = seedScreenshotDemo(credentials); const baseUrl = await resolveBaseUrl(); @@ -26,10 +41,11 @@ test.describe("Map smoke", () => { await loginAndWaitForMap(page, baseUrl, demo.username, demo.password); await expect(page.locator("#map")).toBeVisible(); await expect(page.locator("#map.leaflet-container")).toBeVisible(); - await expect(page.locator("#overlay-six-county-toggle")).toBeVisible(); - await page.locator("#overlay-six-county-toggle").check(); - await expect(page.locator("#overlay-six-county-toggle")).toBeChecked(); - await expect(page.locator("#overlay-putnam-toggle")).not.toBeChecked(); + + // Click report mode and verify panel appears + await page.click("#btn-mode-report"); + await expect(page.locator("#report-panel")).toBeVisible(); + assertNoClientErrors(clientErrors); }); }); diff --git a/geoapp/admin.py b/geoapp/admin.py index 76b5664..6b8cdfe 100644 --- a/geoapp/admin.py +++ b/geoapp/admin.py @@ -1,7 +1,10 @@ from django.contrib import admin from django.contrib.gis.admin import GISModelAdmin -from .models import ActivityLog, CleanupProof, FeedbackEntry, Photo, Profile, RouteCleanup, TrashSite +from .models import ( + ActivityLog, CleanupProof, District, FeedbackEntry, + IPBan, Photo, Profile, RouteCleanup, TrashSite, +) class PhotoInline(admin.TabularInline): @@ -9,6 +12,14 @@ class PhotoInline(admin.TabularInline): extra = 0 +@admin.register(District) +class DistrictAdmin(GISModelAdmin): + list_display = ("name", "slug", "active", "created_at") + list_filter = ("active",) + search_fields = ("name", "slug") + prepopulated_fields = {"slug": ("name",)} + + @admin.register(CleanupProof) class CleanupProofAdmin(admin.ModelAdmin): list_display = ("id", "trash_site", "route_cleanup", "bags_count", "created_by", "created_at") @@ -18,11 +29,11 @@ class CleanupProofAdmin(admin.ModelAdmin): @admin.register(TrashSite) class TrashSiteAdmin(GISModelAdmin): - list_display = ("id", "status", "severity", "hazard_flag", "created_by", "created_at", "cleaned_at") - list_filter = ("status", "severity", "hazard_flag") + list_display = ("id", "status", "severity", "hazard_flag", "district", "created_by", "created_at", "cleaned_at") + list_filter = ("status", "severity", "hazard_flag", "district") search_fields = ("title", "description", "created_by__username") readonly_fields = ("created_at", "updated_at", "cleaned_at") - autocomplete_fields = ("created_by", "claimed_by") + autocomplete_fields = ("created_by", "claimed_by", "district") @admin.register(RouteCleanup) @@ -36,7 +47,8 @@ class RouteCleanupAdmin(GISModelAdmin): @admin.register(Photo) class PhotoAdmin(admin.ModelAdmin): - list_display = ("id", "proof", "created_at") + list_display = ("id", "photo_type", "proof", "created_at") + list_filter = ("photo_type",) autocomplete_fields = ("proof",) @@ -50,10 +62,10 @@ class ProfileAdmin(admin.ModelAdmin): @admin.register(ActivityLog) class ActivityLogAdmin(admin.ModelAdmin): - list_display = ("activity_type", "actor", "trash_site", "route_cleanup", "created_at") + list_display = ("activity_type", "actor", "trash_site", "created_at") list_filter = ("activity_type",) search_fields = ("summary", "actor__username") - autocomplete_fields = ("actor", "trash_site", "route_cleanup", "proof") + autocomplete_fields = ("actor", "trash_site", "proof") readonly_fields = ("created_at",) @@ -64,3 +76,11 @@ class FeedbackEntryAdmin(admin.ModelAdmin): search_fields = ("message", "created_by__username", "page_url") autocomplete_fields = ("created_by",) readonly_fields = ("created_at", "updated_at") + + +@admin.register(IPBan) +class IPBanAdmin(admin.ModelAdmin): + list_display = ("ip_address", "reason", "banned_at", "expires_at", "created_by") + list_filter = ("banned_at",) + search_fields = ("ip_address", "reason") + readonly_fields = ("banned_at",) diff --git a/geoapp/management/commands/seed_district.py b/geoapp/management/commands/seed_district.py new file mode 100644 index 0000000..4d2825d --- /dev/null +++ b/geoapp/management/commands/seed_district.py @@ -0,0 +1,56 @@ +import json + +from django.contrib.gis.geos import GEOSGeometry, MultiPolygon, Polygon +from django.core.management.base import BaseCommand, CommandError + +from geoapp.models import District + + +class Command(BaseCommand): + help = "Load a district boundary from a GeoJSON file into the District table." + + def add_arguments(self, parser): + parser.add_argument("--file", required=True, help="Path to GeoJSON file") + parser.add_argument("--name", required=True, help="District display name") + parser.add_argument("--slug", required=True, help="District URL slug (unique)") + parser.add_argument("--description", default="", help="Optional description") + + def handle(self, *args, **options): + filepath = options["file"] + name = options["name"] + slug = options["slug"] + description = options["description"] + + try: + with open(filepath, "r") as f: + data = json.load(f) + except (FileNotFoundError, json.JSONDecodeError) as exc: + raise CommandError(f"Cannot read GeoJSON file: {exc}") + + features = data.get("features", []) + if not features: + raise CommandError("GeoJSON file contains no features.") + + geom_data = features[0].get("geometry") + if not geom_data: + raise CommandError("First feature has no geometry.") + + geom = GEOSGeometry(json.dumps(geom_data), srid=4326) + + if isinstance(geom, Polygon): + geom = MultiPolygon(geom, srid=4326) + elif not isinstance(geom, MultiPolygon): + raise CommandError(f"Expected Polygon or MultiPolygon, got {geom.geom_type}.") + + district, created = District.objects.update_or_create( + slug=slug, + defaults={ + "name": name, + "geometry": geom, + "description": description, + "active": True, + }, + ) + + action = "Created" if created else "Updated" + self.stdout.write(self.style.SUCCESS(f"{action} district '{district.name}' (slug={district.slug})")) diff --git a/geoapp/middleware.py b/geoapp/middleware.py new file mode 100644 index 0000000..6b96e78 --- /dev/null +++ b/geoapp/middleware.py @@ -0,0 +1,32 @@ +from django.http import JsonResponse +from django.db.models import Q +from django.utils import timezone + + +class IPBanMiddleware: + """Block requests from IP addresses listed in the IPBan table.""" + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + from geoapp.models import IPBan + + ip = self.get_client_ip(request) + is_banned = IPBan.objects.filter( + ip_address=ip, + ).filter( + Q(expires_at__isnull=True) | Q(expires_at__gt=timezone.now()) + ).exists() + + if is_banned: + return JsonResponse({"error": "Access denied."}, status=403) + + return self.get_response(request) + + @staticmethod + def get_client_ip(request): + xff = request.META.get("HTTP_X_FORWARDED_FOR") + if xff: + return xff.split(",")[0].strip() + return request.META.get("REMOTE_ADDR") diff --git a/geoapp/migrations/0003_district_photo_photo_type_trashsite_area_and_more.py b/geoapp/migrations/0003_district_photo_photo_type_trashsite_area_and_more.py new file mode 100644 index 0000000..64b4130 --- /dev/null +++ b/geoapp/migrations/0003_district_photo_photo_type_trashsite_area_and_more.py @@ -0,0 +1,63 @@ +# Generated by Django 5.2.12 on 2026-04-06 14:12 + +import django.contrib.gis.db.models.fields +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('geoapp', '0002_profile_activitylog_feedbackentry'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='District', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=255)), + ('slug', models.SlugField(max_length=100, unique=True)), + ('geometry', django.contrib.gis.db.models.fields.MultiPolygonField(geography=True, srid=4326)), + ('active', models.BooleanField(db_index=True, default=True)), + ('description', models.TextField(blank=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ], + options={ + 'ordering': ['name'], + }, + ), + migrations.AddField( + model_name='photo', + name='photo_type', + field=models.CharField(choices=[('REPORT', 'Report'), ('BEFORE', 'Before'), ('AFTER', 'After')], db_index=True, default='REPORT', max_length=10), + ), + migrations.AddField( + model_name='trashsite', + name='area', + field=django.contrib.gis.db.models.fields.PolygonField(blank=True, geography=True, null=True, srid=4326), + ), + migrations.AddField( + model_name='trashsite', + name='district', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='trash_sites', to='geoapp.district'), + ), + migrations.CreateModel( + name='IPBan', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('ip_address', models.GenericIPAddressField(db_index=True, unique=True)), + ('reason', models.CharField(blank=True, max_length=255)), + ('banned_at', models.DateTimeField(auto_now_add=True)), + ('expires_at', models.DateTimeField(blank=True, null=True)), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ip_bans_created', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-banned_at'], + }, + ), + ] diff --git a/geoapp/models.py b/geoapp/models.py index 6a16280..a0f0594 100644 --- a/geoapp/models.py +++ b/geoapp/models.py @@ -30,6 +30,23 @@ def _line_length_meters(linestring): return distance +class District(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + name = models.CharField(max_length=255) + slug = models.SlugField(max_length=100, unique=True) + geometry = models.MultiPolygonField(geography=True, srid=4326, spatial_index=True) + active = models.BooleanField(default=True, db_index=True) + description = models.TextField(blank=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + ordering = ["name"] + + def __str__(self): + return self.name + + class Profile(models.Model): class Role(models.TextChoices): MEMBER = "MEMBER", "Member" @@ -62,6 +79,10 @@ class Severity(models.TextChoices): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING, db_index=True) location = models.PointField(geography=True, srid=4326, spatial_index=True) + area = models.PolygonField(geography=True, srid=4326, spatial_index=True, null=True, blank=True) + district = models.ForeignKey( + District, on_delete=models.SET_NULL, null=True, blank=True, related_name="trash_sites" + ) title = models.CharField(max_length=255, blank=True) description = models.TextField(blank=True) severity = models.CharField(max_length=10, choices=Severity.choices, blank=True) @@ -130,9 +151,15 @@ def __str__(self): class Photo(models.Model): + class PhotoType(models.TextChoices): + REPORT = "REPORT", "Report" + BEFORE = "BEFORE", "Before" + AFTER = "AFTER", "After" + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) image = models.ImageField(upload_to="proof_photos/%Y/%m/%d") proof = models.ForeignKey(CleanupProof, on_delete=models.CASCADE, related_name="photos") + photo_type = models.CharField(max_length=10, choices=PhotoType.choices, default=PhotoType.REPORT, db_index=True) created_at = models.DateTimeField(auto_now_add=True) class Meta: @@ -193,3 +220,19 @@ class Meta: def __str__(self): return f"{self.feedback_type} from {self.created_by}" + + +class IPBan(models.Model): + ip_address = models.GenericIPAddressField(unique=True, db_index=True) + reason = models.CharField(max_length=255, blank=True) + banned_at = models.DateTimeField(auto_now_add=True) + expires_at = models.DateTimeField(null=True, blank=True) + created_by = models.ForeignKey( + settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name="ip_bans_created" + ) + + class Meta: + ordering = ["-banned_at"] + + def __str__(self): + return f"Ban {self.ip_address}" diff --git a/geoapp/permissions.py b/geoapp/permissions.py index cf650f4..5098145 100644 --- a/geoapp/permissions.py +++ b/geoapp/permissions.py @@ -22,5 +22,13 @@ def can_set_invalid_status(user): return is_admin(user) +def can_submit_report(user): + return bool(user and user.is_authenticated) + + +def can_submit_cleanup(user, site): + return bool(user and user.is_authenticated and site.status != TrashSite.Status.INVALID) + + def can_view_feedback_admin(user): return is_admin(user) diff --git a/geoapp/services.py b/geoapp/services.py index 7c50c0f..f2582c8 100644 --- a/geoapp/services.py +++ b/geoapp/services.py @@ -1,6 +1,4 @@ -from django.db.models import Sum - -from .models import ActivityLog, CleanupProof, RouteCleanup, TrashSite +from .models import ActivityLog, District def log_activity(activity_type, actor, trash_site=None, route_cleanup=None, proof=None, summary=""): @@ -16,22 +14,6 @@ def log_activity(activity_type, actor, trash_site=None, route_cleanup=None, proo ) -def build_user_impact_stats(user): - cleanup_proofs = CleanupProof.objects.filter(created_by=user, trash_site__isnull=False) - route_qs = RouteCleanup.objects.filter(created_by=user) - cleaned_site_ids = cleanup_proofs.values_list("trash_site_id", flat=True) - - totals = cleanup_proofs.aggregate(total_bags=Sum("bags_count")) - route_totals = route_qs.aggregate( - total_distance=Sum("distance_miles"), - total_time=Sum("time_spent_minutes"), - ) - - return { - "reported_sites": TrashSite.objects.filter(created_by=user).count(), - "cleaned_sites": TrashSite.objects.filter(id__in=cleaned_site_ids).distinct().count(), - "logged_routes": route_qs.count(), - "bags_collected": totals["total_bags"] or 0, - "route_miles": float(route_totals["total_distance"] or 0.0), - "time_spent_minutes": route_totals["total_time"] or 0, - } +def assign_district(point): + """Return the first active District whose geometry contains the given point, or None.""" + return District.objects.filter(geometry__covers=point, active=True).first() diff --git a/geoapp/tests.py b/geoapp/tests.py index 19562b7..9334930 100644 --- a/geoapp/tests.py +++ b/geoapp/tests.py @@ -1,84 +1,226 @@ import json from datetime import timedelta +from io import BytesIO +from unittest.mock import patch from django.contrib.auth import get_user_model -from django.contrib.gis.geos import LineString, Point -from django.test import TestCase +from django.contrib.gis.geos import MultiPolygon, Point, Polygon +from django.core.exceptions import ValidationError +from django.core.files.uploadedfile import SimpleUploadedFile +from django.test import TestCase, override_settings from django.urls import reverse from django.utils import timezone -from .models import ActivityLog, CleanupProof, FeedbackEntry, Profile, RouteCleanup, TrashSite -from .services import build_user_impact_stats - +from .middleware import IPBanMiddleware +from .models import ( + ActivityLog, + CleanupProof, + District, + FeedbackEntry, + IPBan, + Photo, + Profile, + TrashSite, +) +from .services import assign_district +from .validators import validate_photo_uploads + +User = get_user_model() + +# Putnam County area (approximate) for test district geometry. +TEST_DISTRICT_POLY = Polygon( + ( + (-85.60, 36.10), + (-85.40, 36.10), + (-85.40, 36.25), + (-85.60, 36.25), + (-85.60, 36.10), + ), + srid=4326, +) +TEST_DISTRICT_GEOM = MultiPolygon(TEST_DISTRICT_POLY, srid=4326) + + +def _tiny_image(name="test.jpg", content_type="image/jpeg"): + """Return a minimal valid JPEG file for upload tests.""" + # Smallest valid JPEG: SOI + APP0 marker + EOI + data = ( + b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00" + b"\xff\xd9" + ) + return SimpleUploadedFile(name, data, content_type=content_type) + + +# --------------------------------------------------------------------------- +# Disable rate limits for all tests except the dedicated RateLimitTests class +# --------------------------------------------------------------------------- +@override_settings(RATELIMIT_ENABLE=False) +class PublicAccessTests(TestCase): + """Public endpoints must be accessible without login.""" + + def test_map_view_is_public(self): + response = self.client.get(reverse("map")) + self.assertEqual(response.status_code, 200) -class AuthGateTests(TestCase): - def setUp(self): - self.user = get_user_model().objects.create_user(username="auth-user", password="pass12345") + def test_cleanups_view_is_public(self): + response = self.client.get(reverse("cleanups")) + self.assertEqual(response.status_code, 200) - def test_unauthenticated_map_redirects_to_login(self): - response = self.client.get(reverse("map")) - self.assertEqual(response.status_code, 302) - self.assertIn("/accounts/login/", response["Location"]) + def test_healthz_is_public(self): + response = self.client.get(reverse("healthz")) + self.assertEqual(response.status_code, 200) + self.assertTrue(response.json()["ok"]) - def test_unauthenticated_api_endpoint_redirects_to_login(self): + def test_features_api_is_public(self): response = self.client.get(reverse("api_features")) - self.assertEqual(response.status_code, 302) - self.assertIn("/accounts/login/", response["Location"]) - - def test_authenticated_map_and_api_access_succeed(self): - self.client.force_login(self.user) - map_response = self.client.get(reverse("map")) - api_response = self.client.get(reverse("api_features")) - self.assertEqual(map_response.status_code, 200) - self.assertEqual(api_response.status_code, 200) - payload = api_response.json() + self.assertEqual(response.status_code, 200) + payload = response.json() self.assertEqual(payload["type"], "FeatureCollection") self.assertIn("features", payload) + def test_districts_api_is_public(self): + response = self.client.get(reverse("api_districts")) + self.assertEqual(response.status_code, 200) + self.assertIn("districts", response.json()) -class RouteCleanupModelTests(TestCase): - def setUp(self): - self.user = get_user_model().objects.create_user(username="route-user", password="pass12345") + def test_cleanups_list_api_is_public(self): + response = self.client.get(reverse("api_cleanups_list")) + self.assertEqual(response.status_code, 200) + self.assertIn("results", response.json()) - def test_distance_miles_is_computed_from_geometry(self): - route = RouteCleanup.objects.create( - geometry=LineString((-85.5010, 36.1627), (-85.4910, 36.1627), srid=4326), - notes="Roadside cleanup", - created_by=self.user, - ) - self.assertGreater(route.distance_miles, 0) - self.assertAlmostEqual(route.distance_miles, 0.56, delta=0.2) +@override_settings(RATELIMIT_ENABLE=False) +class AuthGateTests(TestCase): + """Write endpoints require authentication.""" -class MarkCleanedApiTests(TestCase): - def setUp(self): - self.user = get_user_model().objects.create_user(username="cleanup-user", password="pass12345") - self.client.force_login(self.user) - self.site = TrashSite.objects.create( - location=Point(-85.5016, 36.1627, srid=4326), - description="Trash near trail", - created_by=self.user, + def test_trash_site_create_requires_login(self): + response = self.client.post( + reverse("api_trash_site_create"), + {"lat": "36.16", "lng": "-85.50", "title": "test"}, ) + self.assertEqual(response.status_code, 302) + self.assertIn("/accounts/login/", response["Location"]) - def test_mark_cleaned_sets_status_and_cleaned_at(self): + def test_mark_cleaned_requires_login(self): + user = User.objects.create_user(username="temp", password="pass12345") + site = TrashSite.objects.create( + location=Point(-85.50, 36.16, srid=4326), created_by=user + ) response = self.client.post( - reverse("api_trash_site_mark_cleaned", kwargs={"site_id": self.site.id}), - {"note": "Removed litter", "bags_count": 2}, + reverse("api_trash_site_mark_cleaned", kwargs={"site_id": site.id}) ) + self.assertEqual(response.status_code, 302) + + def test_feedback_requires_login(self): + response = self.client.post( + reverse("api_feedback_create"), + {"message": "test", "feedback_type": "GENERAL"}, + ) + self.assertEqual(response.status_code, 302) + + def test_trash_site_update_requires_login(self): + user = User.objects.create_user(username="temp2", password="pass12345") + site = TrashSite.objects.create( + location=Point(-85.50, 36.16, srid=4326), created_by=user + ) + response = self.client.patch( + reverse("api_trash_site_update", kwargs={"site_id": site.id}), + data=json.dumps({"title": "hack"}), + content_type="application/json", + ) + self.assertEqual(response.status_code, 302) + + +# --------------------------------------------------------------------------- +# District tests +# --------------------------------------------------------------------------- +@override_settings(RATELIMIT_ENABLE=False) +class DistrictModelTests(TestCase): + def test_create_district_with_geometry(self): + district = District.objects.create( + name="Test District", + slug="test-district", + geometry=TEST_DISTRICT_GEOM, + ) + self.assertTrue(district.active) + self.assertEqual(str(district), "Test District") + + def test_assign_district_returns_matching_district(self): + district = District.objects.create( + name="District 3", + slug="district-3", + geometry=TEST_DISTRICT_GEOM, + ) + inside_point = Point(-85.50, 36.16, srid=4326) + result = assign_district(inside_point) + self.assertEqual(result, district) + + def test_assign_district_returns_none_for_outside_point(self): + District.objects.create( + name="District 3", + slug="district-3", + geometry=TEST_DISTRICT_GEOM, + ) + outside_point = Point(-80.00, 30.00, srid=4326) + result = assign_district(outside_point) + self.assertIsNone(result) + + def test_inactive_district_not_assigned(self): + District.objects.create( + name="Inactive District", + slug="inactive", + geometry=TEST_DISTRICT_GEOM, + active=False, + ) + inside_point = Point(-85.50, 36.16, srid=4326) + result = assign_district(inside_point) + self.assertIsNone(result) + + +@override_settings(RATELIMIT_ENABLE=False) +class DistrictApiTests(TestCase): + def test_districts_api_returns_active_districts(self): + District.objects.create( + name="Active District", + slug="active", + geometry=TEST_DISTRICT_GEOM, + active=True, + ) + District.objects.create( + name="Inactive District", + slug="inactive", + geometry=TEST_DISTRICT_GEOM, + active=False, + ) + response = self.client.get(reverse("api_districts")) self.assertEqual(response.status_code, 200) - self.site.refresh_from_db() - self.assertEqual(self.site.status, TrashSite.Status.CLEANED) - self.assertIsNotNone(self.site.cleaned_at) - proof = CleanupProof.objects.get(trash_site=self.site) - self.assertEqual(proof.bags_count, 2) + data = response.json() + self.assertEqual(len(data["districts"]), 1) + self.assertEqual(data["districts"][0]["slug"], "active") + self.assertIn("geometry", data["districts"][0]) + + def test_districts_api_geometry_is_geojson(self): + District.objects.create( + name="District 3", + slug="district-3", + geometry=TEST_DISTRICT_GEOM, + ) + response = self.client.get(reverse("api_districts")) + geom = response.json()["districts"][0]["geometry"] + self.assertIn("type", geom) + self.assertIn("coordinates", geom) +# --------------------------------------------------------------------------- +# Trash site lifecycle +# --------------------------------------------------------------------------- +@override_settings(RATELIMIT_ENABLE=False) class TrashSiteApiLifecycleTests(TestCase): def setUp(self): - self.user = get_user_model().objects.create_user(username="trash-user", password="pass12345") + self.user = User.objects.create_user(username="trash-user", password="pass12345") self.client.force_login(self.user) - def test_create_trash_site_returns_expected_fields_and_geometry(self): + def test_create_trash_site_with_lat_lng(self): response = self.client.post( reverse("api_trash_site_create"), { @@ -100,282 +242,444 @@ def test_create_trash_site_returns_expected_fields_and_geometry(self): site = TrashSite.objects.get(id=payload["id"]) self.assertEqual(site.location.srid, 4326) - self.assertAlmostEqual(site.location.x, -85.5016, places=6) - self.assertAlmostEqual(site.location.y, 36.1627, places=6) + self.assertAlmostEqual(site.location.x, -85.5016, places=4) + self.assertAlmostEqual(site.location.y, 36.1627, places=4) + + def test_create_trash_site_with_geojson_point(self): + geojson = json.dumps({"type": "Point", "coordinates": [-85.50, 36.16]}) + response = self.client.post( + reverse("api_trash_site_create"), + {"geojson": geojson, "title": "Point GeoJSON"}, + ) + self.assertEqual(response.status_code, 201) + payload = response.json() + self.assertEqual(payload["coordinates"], [-85.50, 36.16]) + self.assertIsNone(payload["area"]) + + def test_create_trash_site_auto_assigns_district(self): + district = District.objects.create( + name="District 3", + slug="district-3", + geometry=TEST_DISTRICT_GEOM, + ) + response = self.client.post( + reverse("api_trash_site_create"), + {"lat": "36.16", "lng": "-85.50", "title": "In district"}, + ) + self.assertEqual(response.status_code, 201) + self.assertEqual(response.json()["district"], "district-3") - def test_patch_trash_site_updates_fields_and_cleaned_at_transitions(self): + def test_patch_trash_site_updates_fields(self): site = TrashSite.objects.create( - location=Point(-85.5000, 36.1600, srid=4326), + location=Point(-85.50, 36.16, srid=4326), status=TrashSite.Status.PENDING, - title="Initial title", - description="Initial description", + title="Original", created_by=self.user, ) - - cleaned_payload = { - "status": TrashSite.Status.CLEANED, - "title": "Updated title", - "description": "Updated description", - "severity": TrashSite.Severity.HEAVY, - "hazard_flag": True, - } response = self.client.patch( reverse("api_trash_site_update", kwargs={"site_id": site.id}), - data=json.dumps(cleaned_payload), + data=json.dumps({ + "status": TrashSite.Status.CLEANED, + "title": "Updated", + "severity": TrashSite.Severity.HEAVY, + "hazard_flag": True, + }), content_type="application/json", ) self.assertEqual(response.status_code, 200) - cleaned_response = response.json() - self.assertEqual(cleaned_response["status"], TrashSite.Status.CLEANED) - self.assertEqual(cleaned_response["title"], "Updated title") - self.assertEqual(cleaned_response["severity"], TrashSite.Severity.HEAVY) - self.assertTrue(cleaned_response["hazard_flag"]) - self.assertIsNotNone(cleaned_response["cleaned_at"]) - - pending_payload = {"status": TrashSite.Status.PENDING} + payload = response.json() + self.assertEqual(payload["status"], TrashSite.Status.CLEANED) + self.assertEqual(payload["title"], "Updated") + self.assertIsNotNone(payload["cleaned_at"]) + + def test_cleaned_at_clears_on_status_revert(self): + site = TrashSite.objects.create( + location=Point(-85.50, 36.16, srid=4326), + status=TrashSite.Status.PENDING, + created_by=self.user, + ) + self.client.patch( + reverse("api_trash_site_update", kwargs={"site_id": site.id}), + data=json.dumps({"status": TrashSite.Status.CLEANED}), + content_type="application/json", + ) response = self.client.patch( reverse("api_trash_site_update", kwargs={"site_id": site.id}), - data=json.dumps(pending_payload), + data=json.dumps({"status": TrashSite.Status.PENDING}), content_type="application/json", ) self.assertEqual(response.status_code, 200) - pending_response = response.json() - self.assertEqual(pending_response["status"], TrashSite.Status.PENDING) - self.assertIsNone(pending_response["cleaned_at"]) + self.assertIsNone(response.json()["cleaned_at"]) + def test_detail_api_returns_site_info(self): + site = TrashSite.objects.create( + location=Point(-85.50, 36.16, srid=4326), + title="Detail test", + created_by=self.user, + ) + response = self.client.get( + reverse("api_trash_site_detail", kwargs={"site_id": site.id}) + ) + self.assertEqual(response.status_code, 200) + payload = response.json() + self.assertEqual(payload["id"], str(site.id)) + self.assertIn("photos", payload) + self.assertIn("report", payload["photos"]) -class FeaturesFilterApiTests(TestCase): + def test_detail_api_is_public(self): + site = TrashSite.objects.create( + location=Point(-85.50, 36.16, srid=4326), + title="Public detail", + created_by=self.user, + ) + self.client.logout() + response = self.client.get( + reverse("api_trash_site_detail", kwargs={"site_id": site.id}) + ) + self.assertEqual(response.status_code, 200) + + +# --------------------------------------------------------------------------- +# Polygon area reports +# --------------------------------------------------------------------------- +@override_settings(RATELIMIT_ENABLE=False) +class TrashSitePolygonTests(TestCase): + def setUp(self): + self.user = User.objects.create_user(username="poly-user", password="pass12345") + self.client.force_login(self.user) + + def test_create_with_polygon_geojson(self): + polygon_geojson = json.dumps({ + "type": "Polygon", + "coordinates": [[ + [-85.505, 36.160], + [-85.495, 36.160], + [-85.495, 36.165], + [-85.505, 36.165], + [-85.505, 36.160], + ]], + }) + response = self.client.post( + reverse("api_trash_site_create"), + {"geojson": polygon_geojson, "title": "Area report"}, + ) + self.assertEqual(response.status_code, 201) + payload = response.json() + self.assertIsNotNone(payload["area"]) + # Centroid should be set as the location + self.assertAlmostEqual(payload["coordinates"][0], -85.50, delta=0.01) + self.assertAlmostEqual(payload["coordinates"][1], 36.1625, delta=0.01) + + def test_feature_api_includes_area_flag(self): + poly = Polygon( + ((-85.505, 36.160), (-85.495, 36.160), (-85.495, 36.165), + (-85.505, 36.165), (-85.505, 36.160)), + srid=4326, + ) + TrashSite.objects.create( + location=poly.centroid, + area=poly, + title="Area site", + created_by=self.user, + ) + response = self.client.get( + reverse("api_features"), + {"bbox": "-85.51,36.15,-85.49,36.17"}, + ) + features = response.json()["features"] + self.assertTrue(len(features) > 0) + props = features[0]["properties"] + self.assertTrue(props["has_area"]) + self.assertIn("area_geojson", props) + + +# --------------------------------------------------------------------------- +# Mark cleaned + before/after photos +# --------------------------------------------------------------------------- +@override_settings(RATELIMIT_ENABLE=False) +class MarkCleanedApiTests(TestCase): def setUp(self): - self.user = get_user_model().objects.create_user(username="filter-user", password="pass12345") + self.user = User.objects.create_user(username="cleanup-user", password="pass12345") self.client.force_login(self.user) + self.site = TrashSite.objects.create( + location=Point(-85.5016, 36.1627, srid=4326), + description="Trash near trail", + created_by=self.user, + ) + def test_mark_cleaned_sets_status_and_cleaned_at(self): + response = self.client.post( + reverse("api_trash_site_mark_cleaned", kwargs={"site_id": self.site.id}), + {"note": "Removed litter", "bags_count": 2}, + ) + self.assertEqual(response.status_code, 200) + self.site.refresh_from_db() + self.assertEqual(self.site.status, TrashSite.Status.CLEANED) + self.assertIsNotNone(self.site.cleaned_at) + proof = CleanupProof.objects.get(trash_site=self.site) + self.assertEqual(proof.bags_count, 2) + + def test_before_and_after_photos_get_correct_type(self): + before_img = _tiny_image("before.jpg") + after_img = _tiny_image("after.jpg") + response = self.client.post( + reverse("api_trash_site_mark_cleaned", kwargs={"site_id": self.site.id}), + { + "note": "Cleaned up", + "bags_count": 1, + "before_photos": before_img, + "after_photos": after_img, + }, + ) + self.assertEqual(response.status_code, 200) + proof = CleanupProof.objects.get(trash_site=self.site) + before_photos = proof.photos.filter(photo_type=Photo.PhotoType.BEFORE) + after_photos = proof.photos.filter(photo_type=Photo.PhotoType.AFTER) + self.assertEqual(before_photos.count(), 1) + self.assertEqual(after_photos.count(), 1) + + def test_invalid_site_cannot_be_marked_cleaned(self): + self.site.status = TrashSite.Status.INVALID + self.site.save() + response = self.client.post( + reverse("api_trash_site_mark_cleaned", kwargs={"site_id": self.site.id}), + {"note": "Attempt"}, + ) + self.assertEqual(response.status_code, 403) + + +# --------------------------------------------------------------------------- +# Features filter API +# --------------------------------------------------------------------------- +@override_settings(RATELIMIT_ENABLE=False) +class FeaturesFilterApiTests(TestCase): + def setUp(self): + self.user = User.objects.create_user(username="filter-user", password="pass12345") self.in_bbox_pending = TrashSite.objects.create( location=Point(-85.5020, 36.1630, srid=4326), status=TrashSite.Status.PENDING, created_by=self.user, ) - self.in_bbox_cleaned_recent = TrashSite.objects.create( + self.in_bbox_cleaned = TrashSite.objects.create( location=Point(-85.4995, 36.1620, srid=4326), status=TrashSite.Status.CLEANED, created_by=self.user, ) - self.in_bbox_cleaned_old = TrashSite.objects.create( + self.in_bbox_old = TrashSite.objects.create( location=Point(-85.4980, 36.1610, srid=4326), status=TrashSite.Status.CLEANED, created_by=self.user, ) - TrashSite.objects.filter(id=self.in_bbox_cleaned_old.id).update(created_at=timezone.now() - timedelta(days=10)) - self.in_bbox_cleaned_old.refresh_from_db() - + TrashSite.objects.filter(id=self.in_bbox_old.id).update( + created_at=timezone.now() - timedelta(days=10) + ) self.out_of_bbox = TrashSite.objects.create( location=Point(-85.7000, 36.3000, srid=4326), status=TrashSite.Status.PENDING, created_by=self.user, ) - def _trash_site_ids(self, payload): - return { - feature["properties"]["id"] - for feature in payload["features"] - if feature["properties"]["type"] == "trash_site" - } + def _ids(self, payload): + return {f["properties"]["id"] for f in payload["features"]} - def test_bbox_returns_only_in_bounds_features(self): + def test_bbox_filters_features(self): response = self.client.get( reverse("api_features"), {"bbox": "-85.51,36.15,-85.49,36.17", "days": "all"}, ) self.assertEqual(response.status_code, 200) - ids = self._trash_site_ids(response.json()) + ids = self._ids(response.json()) self.assertIn(str(self.in_bbox_pending.id), ids) - self.assertIn(str(self.in_bbox_cleaned_recent.id), ids) - self.assertIn(str(self.in_bbox_cleaned_old.id), ids) self.assertNotIn(str(self.out_of_bbox.id), ids) - def test_status_and_date_filters_return_expected_subset(self): - cleaned_response = self.client.get( + def test_status_filter(self): + response = self.client.get( reverse("api_features"), {"bbox": "-85.51,36.15,-85.49,36.17", "status": "CLEANED", "days": "all"}, ) - self.assertEqual(cleaned_response.status_code, 200) - cleaned_ids = self._trash_site_ids(cleaned_response.json()) - self.assertNotIn(str(self.in_bbox_pending.id), cleaned_ids) - self.assertIn(str(self.in_bbox_cleaned_recent.id), cleaned_ids) - self.assertIn(str(self.in_bbox_cleaned_old.id), cleaned_ids) + ids = self._ids(response.json()) + self.assertNotIn(str(self.in_bbox_pending.id), ids) + self.assertIn(str(self.in_bbox_cleaned.id), ids) - recent_cleaned_response = self.client.get( + def test_days_filter(self): + response = self.client.get( reverse("api_features"), {"bbox": "-85.51,36.15,-85.49,36.17", "status": "CLEANED", "days": "7"}, ) - self.assertEqual(recent_cleaned_response.status_code, 200) - recent_cleaned_ids = self._trash_site_ids(recent_cleaned_response.json()) - self.assertIn(str(self.in_bbox_cleaned_recent.id), recent_cleaned_ids) - self.assertNotIn(str(self.in_bbox_cleaned_old.id), recent_cleaned_ids) - - -class RouteCleanupApiTests(TestCase): - def setUp(self): - self.user = get_user_model().objects.create_user(username="route-api-user", password="pass12345") - self.client.force_login(self.user) + ids = self._ids(response.json()) + self.assertIn(str(self.in_bbox_cleaned.id), ids) + self.assertNotIn(str(self.in_bbox_old.id), ids) - def test_create_route_get_detail_and_list_in_features_bbox(self): - create_response = self.client.post( - reverse("api_route_cleanup_create"), - data=json.dumps( - { - "coordinates": [[-85.5010, 36.1620], [-85.4980, 36.1630], [-85.4950, 36.1640]], - "notes": "Road shoulder cleanup", - "time_spent_minutes": 35, - } - ), - content_type="application/json", + def test_district_filter(self): + district = District.objects.create( + name="D3", slug="district-3", geometry=TEST_DISTRICT_GEOM ) - self.assertEqual(create_response.status_code, 201) - created = create_response.json() - route_id = created["id"] - self.assertIn("distance_miles", created) - self.assertGreater(created["distance_miles"], 0) - - detail_response = self.client.get(reverse("api_route_cleanup_detail_root", kwargs={"route_id": route_id})) - self.assertEqual(detail_response.status_code, 200) - detail = detail_response.json() - self.assertEqual(detail["id"], route_id) - self.assertGreater(detail["distance_miles"], 0) - self.assertGreaterEqual(len(detail["coordinates"]), 2) - - feature_response = self.client.get( + self.in_bbox_pending.district = district + self.in_bbox_pending.save() + response = self.client.get( reverse("api_features"), - {"bbox": "-85.51,36.15,-85.49,36.17", "days": "all"}, + {"district": "district-3", "days": "all"}, ) - self.assertEqual(feature_response.status_code, 200) - route_ids = { - feature["properties"]["id"] - for feature in feature_response.json()["features"] - if feature["properties"]["type"] == "route_cleanup" - } - self.assertIn(route_id, route_ids) + ids = self._ids(response.json()) + self.assertIn(str(self.in_bbox_pending.id), ids) + self.assertNotIn(str(self.in_bbox_cleaned.id), ids) + def test_no_route_features(self): + """Features API should only return trash_site types, not routes.""" + response = self.client.get(reverse("api_features")) + for feature in response.json()["features"]: + self.assertEqual(feature["properties"]["type"], "trash_site") -class ApiSurfaceContractTests(TestCase): + +# --------------------------------------------------------------------------- +# Cleanups page and API +# --------------------------------------------------------------------------- +@override_settings(RATELIMIT_ENABLE=False) +class CleanupsPageTests(TestCase): def setUp(self): - self.user = get_user_model().objects.create_user(username="contract-user", password="pass12345") - self.client.force_login(self.user) - self.site = TrashSite.objects.create( - location=Point(-85.5016, 36.1627, srid=4326), + self.user = User.objects.create_user(username="cleanups-user", password="pass12345") + + def test_cleanups_page_renders(self): + response = self.client.get(reverse("cleanups")) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Cleanup") + + def test_cleanups_list_api_returns_only_cleaned(self): + TrashSite.objects.create( + location=Point(-85.50, 36.16, srid=4326), status=TrashSite.Status.PENDING, created_by=self.user, ) - self.route = RouteCleanup.objects.create( - geometry=LineString((-85.5010, 36.1620), (-85.4980, 36.1630), srid=4326), + cleaned = TrashSite.objects.create( + location=Point(-85.50, 36.16, srid=4326), + status=TrashSite.Status.CLEANED, + cleaned_at=timezone.now(), created_by=self.user, ) - - def test_core_get_endpoints_return_expected_json_shapes(self): - features_response = self.client.get( - reverse("api_features"), - {"bbox": "-85.51,36.15,-85.49,36.17", "days": "all"}, + response = self.client.get(reverse("api_cleanups_list")) + self.assertEqual(response.status_code, 200) + data = response.json() + result_ids = {r["id"] for r in data["results"]} + self.assertIn(str(cleaned.id), result_ids) + self.assertEqual(len(result_ids), 1) + + def test_cleanups_list_api_pagination(self): + for i in range(5): + TrashSite.objects.create( + location=Point(-85.50, 36.16, srid=4326), + status=TrashSite.Status.CLEANED, + cleaned_at=timezone.now(), + created_by=self.user, + ) + response = self.client.get( + reverse("api_cleanups_list"), {"page_size": 2, "page": 1} ) - self.assertEqual(features_response.status_code, 200) - features_payload = features_response.json() - self.assertEqual(features_payload["type"], "FeatureCollection") - self.assertIn("features", features_payload) - - trash_response = self.client.get(reverse("api_trash_site_update", kwargs={"site_id": self.site.id})) - self.assertEqual(trash_response.status_code, 200) - trash_payload = trash_response.json() - self.assertEqual(trash_payload["id"], str(self.site.id)) - self.assertIn("status", trash_payload) - self.assertIn("coordinates", trash_payload) - - route_response = self.client.get(reverse("api_route_cleanup_detail_root", kwargs={"route_id": self.route.id})) - self.assertEqual(route_response.status_code, 200) - route_payload = route_response.json() - self.assertEqual(route_payload["id"], str(self.route.id)) - self.assertIn("distance_miles", route_payload) - self.assertIn("coordinates", route_payload) + data = response.json() + self.assertEqual(data["count"], 5) + self.assertEqual(len(data["results"]), 2) + self.assertEqual(data["num_pages"], 3) +# --------------------------------------------------------------------------- +# Validation tests +# --------------------------------------------------------------------------- +@override_settings(RATELIMIT_ENABLE=False) class ApiValidationTests(TestCase): def setUp(self): - self.user = get_user_model().objects.create_user(username="validation-user", password="pass12345") + self.user = User.objects.create_user(username="val-user", password="pass12345") self.client.force_login(self.user) self.site = TrashSite.objects.create( - location=Point(-85.5016, 36.1627, srid=4326), - created_by=self.user, + location=Point(-85.50, 36.16, srid=4326), created_by=self.user ) - def test_invalid_route_geometry_payload_returns_json_error(self): - response = self.client.post( - reverse("api_route_cleanup_create"), - data=json.dumps({"coordinates": [[-85.50, 36.16]]}), + def test_invalid_status_patch_returns_error(self): + response = self.client.patch( + reverse("api_trash_site_update", kwargs={"site_id": self.site.id}), + data=json.dumps({"status": "NOT_A_REAL_STATUS"}), content_type="application/json", ) self.assertGreaterEqual(response.status_code, 400) self.assertIn("error", response.json()) - def test_invalid_trash_status_patch_returns_json_error(self): - response = self.client.patch( - reverse("api_trash_site_update", kwargs={"site_id": self.site.id}), - data=json.dumps({"status": "NOT_A_REAL_STATUS"}), - content_type="application/json", + def test_invalid_severity_on_create_returns_error(self): + response = self.client.post( + reverse("api_trash_site_create"), + {"lat": "36.16", "lng": "-85.50", "severity": "EXTREME"}, ) self.assertGreaterEqual(response.status_code, 400) self.assertIn("error", response.json()) + def test_missing_coordinates_returns_error(self): + response = self.client.post( + reverse("api_trash_site_create"), {"title": "No coords"} + ) + self.assertGreaterEqual(response.status_code, 400) -class HealthAndFeedbackTests(TestCase): - def setUp(self): - self.user = get_user_model().objects.create_user(username="feedback-user", password="pass12345") + def test_invalid_geojson_type_returns_error(self): + geojson = json.dumps({"type": "LineString", "coordinates": [[-85.5, 36.16], [-85.49, 36.16]]}) + response = self.client.post( + reverse("api_trash_site_create"), + {"geojson": geojson, "title": "Bad type"}, + ) + self.assertGreaterEqual(response.status_code, 400) + self.assertIn("error", response.json()) - def test_healthz_is_public_and_reports_ok(self): - response = self.client.get(reverse("healthz")) - self.assertEqual(response.status_code, 200) - self.assertTrue(response.json()["ok"]) - def test_feedback_submission_creates_entry(self): - self.client.force_login(self.user) - response = self.client.post( - reverse("api_feedback_create"), - { - "feedback_type": FeedbackEntry.FeedbackType.BUG, - "message": "Map did not recenter after route save.", - "page_url": "/map/", - }, +# --------------------------------------------------------------------------- +# Photo upload validation +# --------------------------------------------------------------------------- +class PhotoUploadValidationTests(TestCase): + def test_max_count_exceeded(self): + files = [_tiny_image(f"img{i}.jpg") for i in range(6)] + with self.assertRaises(ValidationError): + validate_photo_uploads(files) + + def test_max_size_exceeded(self): + big_file = SimpleUploadedFile( + "big.jpg", + b"\xff\xd8" + b"\x00" * (11 * 1024 * 1024), + content_type="image/jpeg", ) - self.assertEqual(response.status_code, 201) - payload = response.json() - self.assertEqual(payload["feedback_type"], FeedbackEntry.FeedbackType.BUG) - self.assertEqual(FeedbackEntry.objects.count(), 1) - entry = FeedbackEntry.objects.get() - self.assertEqual(entry.created_by, self.user) - self.assertEqual(entry.page_url, "/map/") + with self.assertRaises(ValidationError): + validate_photo_uploads([big_file]) + + def test_invalid_mime_type(self): + bad_file = SimpleUploadedFile("doc.pdf", b"%PDF-1.4", content_type="application/pdf") + with self.assertRaises(ValidationError): + validate_photo_uploads([bad_file]) + def test_valid_files_pass(self): + files = [_tiny_image(f"ok{i}.jpg") for i in range(5)] + validate_photo_uploads(files) # Should not raise + +# --------------------------------------------------------------------------- +# Permission / role tests +# --------------------------------------------------------------------------- +@override_settings(RATELIMIT_ENABLE=False) class PermissionRoleTests(TestCase): def setUp(self): - user_model = get_user_model() - self.owner = user_model.objects.create_user(username="owner-user", password="pass12345") - self.other_user = user_model.objects.create_user(username="other-user", password="pass12345") - self.admin_user = user_model.objects.create_user(username="admin-user", password="pass12345") - self.admin_user.profile.role = Profile.Role.ADMIN - self.admin_user.profile.save(update_fields=["role"]) + self.owner = User.objects.create_user(username="owner", password="pass12345") + self.other = User.objects.create_user(username="other", password="pass12345") + self.admin = User.objects.create_user(username="admin", password="pass12345") + self.admin.profile.role = Profile.Role.ADMIN + self.admin.profile.save(update_fields=["role"]) self.site = TrashSite.objects.create( - location=Point(-85.5016, 36.1627, srid=4326), + location=Point(-85.50, 36.16, srid=4326), title="Owner site", created_by=self.owner, ) - def test_non_owner_cannot_patch_other_users_site(self): - self.client.force_login(self.other_user) + def test_non_owner_cannot_patch(self): + self.client.force_login(self.other) response = self.client.patch( reverse("api_trash_site_update", kwargs={"site_id": self.site.id}), - data=json.dumps({"title": "Unauthorized change"}), + data=json.dumps({"title": "Nope"}), content_type="application/json", ) self.assertEqual(response.status_code, 403) - self.assertIn("error", response.json()) - def test_non_admin_cannot_invalidate_site(self): + def test_non_admin_cannot_invalidate(self): self.client.force_login(self.owner) response = self.client.patch( reverse("api_trash_site_update", kwargs={"site_id": self.site.id}), @@ -383,10 +687,9 @@ def test_non_admin_cannot_invalidate_site(self): content_type="application/json", ) self.assertEqual(response.status_code, 403) - self.assertIn("error", response.json()) - def test_admin_can_invalidate_site(self): - self.client.force_login(self.admin_user) + def test_admin_can_invalidate(self): + self.client.force_login(self.admin) response = self.client.patch( reverse("api_trash_site_update", kwargs={"site_id": self.site.id}), data=json.dumps({"status": TrashSite.Status.INVALID}), @@ -397,53 +700,207 @@ def test_admin_can_invalidate_site(self): self.assertEqual(self.site.status, TrashSite.Status.INVALID) -class ActivityAndImpactTests(TestCase): +# --------------------------------------------------------------------------- +# IP ban middleware +# --------------------------------------------------------------------------- +class IPBanMiddlewareTests(TestCase): + def test_banned_ip_returns_403(self): + IPBan.objects.create(ip_address="1.2.3.4", reason="Spam") + response = self.client.get( + reverse("healthz"), REMOTE_ADDR="1.2.3.4" + ) + self.assertEqual(response.status_code, 403) + self.assertIn("error", response.json()) + + def test_expired_ban_passes(self): + IPBan.objects.create( + ip_address="1.2.3.5", + reason="Expired", + expires_at=timezone.now() - timedelta(hours=1), + ) + response = self.client.get( + reverse("healthz"), REMOTE_ADDR="1.2.3.5" + ) + self.assertEqual(response.status_code, 200) + + def test_future_expiry_still_banned(self): + IPBan.objects.create( + ip_address="1.2.3.6", + reason="Active", + expires_at=timezone.now() + timedelta(hours=1), + ) + response = self.client.get( + reverse("healthz"), REMOTE_ADDR="1.2.3.6" + ) + self.assertEqual(response.status_code, 403) + + def test_clean_ip_passes(self): + response = self.client.get( + reverse("healthz"), REMOTE_ADDR="9.9.9.9" + ) + self.assertEqual(response.status_code, 200) + + def test_xff_header_used(self): + IPBan.objects.create(ip_address="10.0.0.1", reason="XFF test") + response = self.client.get( + reverse("healthz"), + REMOTE_ADDR="127.0.0.1", + HTTP_X_FORWARDED_FOR="10.0.0.1, 192.168.1.1", + ) + self.assertEqual(response.status_code, 403) + + +# --------------------------------------------------------------------------- +# Feedback +# --------------------------------------------------------------------------- +@override_settings(RATELIMIT_ENABLE=False) +class FeedbackTests(TestCase): def setUp(self): - self.user = get_user_model().objects.create_user(username="activity-user", password="pass12345") + self.user = User.objects.create_user(username="fb-user", password="pass12345") + + def test_feedback_submission_creates_entry(self): self.client.force_login(self.user) + response = self.client.post( + reverse("api_feedback_create"), + { + "feedback_type": FeedbackEntry.FeedbackType.BUG, + "message": "Map did not recenter.", + "page_url": "/", + }, + ) + self.assertEqual(response.status_code, 201) + payload = response.json() + self.assertEqual(payload["feedback_type"], FeedbackEntry.FeedbackType.BUG) + self.assertEqual(FeedbackEntry.objects.count(), 1) - def test_activity_log_and_impact_stats_cover_report_cleanup_and_route(self): - create_site_response = self.client.post( - reverse("api_trash_site_create"), + def test_feedback_missing_message_returns_error(self): + self.client.force_login(self.user) + response = self.client.post( + reverse("api_feedback_create"), + {"feedback_type": "GENERAL", "message": ""}, + ) + self.assertGreaterEqual(response.status_code, 400) + + +# --------------------------------------------------------------------------- +# Signup +# --------------------------------------------------------------------------- +@override_settings(RATELIMIT_ENABLE=False) +class SignupTests(TestCase): + def test_signup_page_renders(self): + response = self.client.get(reverse("signup")) + self.assertEqual(response.status_code, 200) + + def test_signup_creates_user_and_logs_in(self): + response = self.client.post( + reverse("signup"), { - "lat": "36.1627", - "lng": "-85.5016", - "title": "Roadside litter", + "username": "newuser", + "password1": "Str0ngP@ss!", + "password2": "Str0ngP@ss!", }, ) - self.assertEqual(create_site_response.status_code, 201) - site_id = create_site_response.json()["id"] + self.assertEqual(response.status_code, 302) + self.assertEqual(response["Location"], "/") + self.assertTrue(User.objects.filter(username="newuser").exists()) + + def test_authenticated_user_redirected_from_signup(self): + user = User.objects.create_user(username="existing", password="pass12345") + self.client.force_login(user) + response = self.client.get(reverse("signup")) + self.assertEqual(response.status_code, 302) + self.assertEqual(response["Location"], "/") + + +# --------------------------------------------------------------------------- +# API surface contract +# --------------------------------------------------------------------------- +@override_settings(RATELIMIT_ENABLE=False) +class ApiSurfaceContractTests(TestCase): + def setUp(self): + self.user = User.objects.create_user(username="contract-user", password="pass12345") + self.client.force_login(self.user) + self.site = TrashSite.objects.create( + location=Point(-85.5016, 36.1627, srid=4326), + status=TrashSite.Status.PENDING, + created_by=self.user, + ) - mark_cleaned_response = self.client.post( + def test_features_api_shape(self): + response = self.client.get(reverse("api_features")) + self.assertEqual(response.status_code, 200) + payload = response.json() + self.assertEqual(payload["type"], "FeatureCollection") + self.assertIn("features", payload) + if payload["features"]: + feature = payload["features"][0] + self.assertIn("geometry", feature) + self.assertIn("properties", feature) + props = feature["properties"] + for key in ("id", "type", "status", "severity", "has_area"): + self.assertIn(key, props) + + def test_detail_api_shape(self): + response = self.client.get( + reverse("api_trash_site_detail", kwargs={"site_id": self.site.id}) + ) + self.assertEqual(response.status_code, 200) + payload = response.json() + for key in ("id", "status", "coordinates", "photos", "proofs", "permissions", "district"): + self.assertIn(key, payload) + self.assertIn("report", payload["photos"]) + self.assertIn("before", payload["photos"]) + self.assertIn("after", payload["photos"]) + + def test_cleanups_list_api_shape(self): + response = self.client.get(reverse("api_cleanups_list")) + self.assertEqual(response.status_code, 200) + payload = response.json() + for key in ("count", "page", "num_pages", "results"): + self.assertIn(key, payload) + + def test_districts_api_shape(self): + District.objects.create( + name="D3", slug="d3", geometry=TEST_DISTRICT_GEOM + ) + response = self.client.get(reverse("api_districts")) + payload = response.json() + self.assertIn("districts", payload) + d = payload["districts"][0] + for key in ("id", "name", "slug", "geometry"): + self.assertIn(key, d) + + def test_removed_endpoints_return_404(self): + """Verify removed route/activity endpoints no longer resolve.""" + for name in ("api_route_cleanup_create", "api_route_cleanup_detail_root", "api_activity"): + with self.assertRaises(Exception): + reverse(name) + + +# --------------------------------------------------------------------------- +# Activity logging (integration) +# --------------------------------------------------------------------------- +@override_settings(RATELIMIT_ENABLE=False) +class ActivityLogTests(TestCase): + def setUp(self): + self.user = User.objects.create_user(username="activity-user", password="pass12345") + self.client.force_login(self.user) + + def test_report_and_cleanup_create_activity_logs(self): + response = self.client.post( + reverse("api_trash_site_create"), + {"lat": "36.16", "lng": "-85.50", "title": "Test"}, + ) + self.assertEqual(response.status_code, 201) + site_id = response.json()["id"] + + self.client.post( reverse("api_trash_site_mark_cleaned", kwargs={"site_id": site_id}), - {"note": "Cleared", "bags_count": 3}, - ) - self.assertEqual(mark_cleaned_response.status_code, 200) - - route_response = self.client.post( - reverse("api_route_cleanup_create"), - data=json.dumps( - { - "coordinates": [[-85.5010, 36.1620], [-85.4980, 36.1630]], - "notes": "Neighborhood cleanup", - "time_spent_minutes": 25, - } - ), - content_type="application/json", + {"note": "Done", "bags_count": 1}, + ) + + types = set( + ActivityLog.objects.values_list("activity_type", flat=True) ) - self.assertEqual(route_response.status_code, 201) - - activity_response = self.client.get(reverse("api_activity"), {"page_size": "10"}) - self.assertEqual(activity_response.status_code, 200) - activity_types = {item["activity_type"] for item in activity_response.json()["results"]} - self.assertIn(ActivityLog.ActivityType.TRASH_REPORTED, activity_types) - self.assertIn(ActivityLog.ActivityType.TRASH_CLEANED, activity_types) - self.assertIn(ActivityLog.ActivityType.ROUTE_LOGGED, activity_types) - - stats = build_user_impact_stats(self.user) - self.assertEqual(stats["reported_sites"], 1) - self.assertEqual(stats["cleaned_sites"], 1) - self.assertEqual(stats["bags_collected"], 3) - self.assertEqual(stats["logged_routes"], 1) - self.assertGreater(stats["route_miles"], 0) - self.assertEqual(stats["time_spent_minutes"], 25) + self.assertIn(ActivityLog.ActivityType.TRASH_REPORTED, types) + self.assertIn(ActivityLog.ActivityType.TRASH_CLEANED, types) diff --git a/geoapp/urls.py b/geoapp/urls.py index 84b60d0..f235264 100644 --- a/geoapp/urls.py +++ b/geoapp/urls.py @@ -3,19 +3,20 @@ from . import views urlpatterns = [ - path("", views.home, name="home"), + # Public HTML + path("", views.map_view, name="map"), + path("cleanups/", views.cleanups_view, name="cleanups"), path("healthz", views.healthz, name="healthz"), - path("map/", views.map_view, name="map"), - path("updates/", views.updates_view, name="updates"), - path("impact/", views.impact_view, name="impact"), - path("api/activity/", views.activity_api, name="api_activity"), - path("api/feedback/", views.feedback_create_api, name="api_feedback_create"), + + # Public JSON API path("api/features/", views.features_api, name="api_features"), + path("api/districts/", views.districts_api, name="api_districts"), + path("api/trash-sites//detail/", views.trash_site_detail_api, name="api_trash_site_detail"), + path("api/cleanups/", views.cleanups_list_api, name="api_cleanups_list"), + + # Authenticated JSON API path("api/trash-sites/", views.trash_site_create_api, name="api_trash_site_create"), path("api/trash-sites//", views.trash_site_update_api, name="api_trash_site_update"), - path("api/trash-sites//detail/", views.trash_site_detail_api, name="api_trash_site_detail"), path("api/trash-sites//mark-cleaned/", views.trash_site_mark_cleaned_api, name="api_trash_site_mark_cleaned"), - path("api/route-cleanups/", views.route_cleanup_create_api, name="api_route_cleanup_create"), - path("api/route-cleanups//", views.route_cleanup_detail_api, name="api_route_cleanup_detail_root"), - path("api/route-cleanups//detail/", views.route_cleanup_detail_api, name="api_route_cleanup_detail"), + path("api/feedback/", views.feedback_create_api, name="api_feedback_create"), ] diff --git a/geoapp/validators.py b/geoapp/validators.py new file mode 100644 index 0000000..5479218 --- /dev/null +++ b/geoapp/validators.py @@ -0,0 +1,19 @@ +from django.core.exceptions import ValidationError + +MAX_PHOTO_COUNT = 5 +MAX_PHOTO_SIZE_BYTES = 10 * 1024 * 1024 # 10 MB +ALLOWED_IMAGE_TYPES = {"image/jpeg", "image/png", "image/webp", "image/heic"} + + +def validate_photo_uploads(files, max_count=MAX_PHOTO_COUNT): + """Validate a list of uploaded photo files for count, size, and MIME type.""" + if len(files) > max_count: + raise ValidationError(f"Maximum {max_count} photos allowed per submission.") + for f in files: + if f.size > MAX_PHOTO_SIZE_BYTES: + raise ValidationError(f"Each photo must be under 10 MB. '{f.name}' is too large.") + if f.content_type not in ALLOWED_IMAGE_TYPES: + raise ValidationError( + f"Unsupported image type '{f.content_type}' for '{f.name}'. " + f"Allowed: JPEG, PNG, WebP, HEIC." + ) diff --git a/geoapp/views.py b/geoapp/views.py index 666d363..11c5f4e 100644 --- a/geoapp/views.py +++ b/geoapp/views.py @@ -2,18 +2,28 @@ from datetime import timedelta from django.contrib.auth.decorators import login_required +from django.contrib.auth.forms import UserCreationForm +from django.contrib.auth import login +from django.core.exceptions import ValidationError from django.core.paginator import Paginator -from django.contrib.gis.geos import LineString, Point, Polygon +from django.contrib.gis.geos import GEOSGeometry, MultiPolygon, Point, Polygon from django.db import connection from django.http import JsonResponse from django.shortcuts import get_object_or_404, redirect, render from django.utils import timezone from django.views.decorators.http import require_GET, require_http_methods -from .models import ActivityLog, CleanupProof, FeedbackEntry, Photo, RouteCleanup, TrashSite +from django_ratelimit.decorators import ratelimit + +from .models import ActivityLog, CleanupProof, District, FeedbackEntry, Photo, TrashSite from .permissions import can_edit_trash_site, can_mark_cleaned, can_set_invalid_status, is_admin -from .services import build_user_impact_stats, log_activity +from .services import assign_district, log_activity +from .validators import validate_photo_uploads + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- def _json_error(message, status=400): return JsonResponse({"error": message}, status=status) @@ -78,16 +88,41 @@ def _days_to_cutoff(days_raw): return timezone.now() - timedelta(days=days) -def _photo_urls_for_proofs(proofs): - urls = [] +def _photo_list_for_proof(proof): + return [ + { + "url": photo.image.url, + "type": photo.photo_type, + } + for photo in proof.photos.all() + if photo.image + ] + + +def _photo_urls_grouped(proofs): + """Return photos grouped by type: report, before, after.""" + groups = {"report": [], "before": [], "after": []} for proof in proofs: for photo in proof.photos.all(): if photo.image: - urls.append(photo.image.url) - return urls + key = photo.photo_type.lower() + if key in groups: + groups[key].append(photo.image.url) + else: + groups["report"].append(photo.image.url) + return groups + +# --------------------------------------------------------------------------- +# Serializers +# --------------------------------------------------------------------------- def _serialize_trash_site(site, user=None): + proofs_qs = site.proofs.prefetch_related("photos", "created_by").all() + area_geojson = None + if site.area: + area_geojson = json.loads(site.area.geojson) + return { "id": str(site.id), "status": site.status, @@ -101,12 +136,14 @@ def _serialize_trash_site(site, user=None): "updated_at": site.updated_at.isoformat(), "cleaned_at": site.cleaned_at.isoformat() if site.cleaned_at else None, "coordinates": [site.location.x, site.location.y], + "area": area_geojson, + "district": site.district.slug if site.district else None, "permissions": { - "can_edit": can_edit_trash_site(user, site) if user else False, - "can_mark_cleaned": can_mark_cleaned(user, site) if user else False, - "can_invalidate": can_set_invalid_status(user) if user else False, + "can_edit": can_edit_trash_site(user, site) if user and user.is_authenticated else False, + "can_mark_cleaned": can_mark_cleaned(user, site) if user and user.is_authenticated else False, + "can_invalidate": can_set_invalid_status(user) if user and user.is_authenticated else False, }, - "photos": _photo_urls_for_proofs(site.proofs.prefetch_related("photos")), + "photos": _photo_urls_grouped(proofs_qs), "proofs": [ { "id": str(proof.id), @@ -114,110 +151,78 @@ def _serialize_trash_site(site, user=None): "bags_count": proof.bags_count, "created_by": proof.created_by.username, "created_at": proof.created_at.isoformat(), - "photos": [photo.image.url for photo in proof.photos.all() if photo.image], + "photos": _photo_list_for_proof(proof), } - for proof in site.proofs.prefetch_related("photos", "created_by").all() + for proof in proofs_qs ], } -def _serialize_route(route, user=None): - return { - "id": str(route.id), - "status": route.status, - "notes": route.notes, - "distance_miles": route.distance_miles, - "time_spent_minutes": route.time_spent_minutes, - "created_by": route.created_by.username, - "created_at": route.created_at.isoformat(), - "coordinates": list(route.geometry.coords), - "permissions": { - "is_admin": is_admin(user) if user else False, - }, - "photos": _photo_urls_for_proofs(route.proofs.prefetch_related("photos")), - "proofs": [ - { - "id": str(proof.id), - "note": proof.note, - "bags_count": proof.bags_count, - "created_by": proof.created_by.username, - "created_at": proof.created_at.isoformat(), - "photos": [photo.image.url for photo in proof.photos.all() if photo.image], - } - for proof in route.proofs.prefetch_related("photos", "created_by").all() - ], - } - - -def _serialize_activity(activity): - focus_type = "" - focus_id = "" - if activity.trash_site_id: - focus_type = "trash_site" - focus_id = str(activity.trash_site_id) - elif activity.route_cleanup_id: - focus_type = "route_cleanup" - focus_id = str(activity.route_cleanup_id) - - return { - "id": str(activity.id), - "activity_type": activity.activity_type, - "activity_label": activity.get_activity_type_display(), - "actor": activity.actor.username, - "summary": activity.summary, - "created_at": activity.created_at.isoformat(), - "focus_type": focus_type, - "focus_id": focus_id, +def _site_to_feature(site, user=None): + props = { + "id": str(site.id), + "type": "trash_site", + "status": site.status, + "title": site.title, + "description": site.description, + "severity": site.severity, + "hazard_flag": site.hazard_flag, + "cleaned_at": site.cleaned_at.isoformat() if site.cleaned_at else None, + "created_at": site.created_at.isoformat(), + "has_area": bool(site.area), + "photo_count": sum(p.photos.count() for p in site.proofs.all()), } + if user and user.is_authenticated: + props["can_mark_cleaned"] = can_mark_cleaned(user, site) + else: + props["can_mark_cleaned"] = False - -def _site_to_feature(site, user=None): - return { + feature = { "type": "Feature", "geometry": {"type": "Point", "coordinates": [site.location.x, site.location.y]}, - "properties": { - "id": str(site.id), - "type": "trash_site", - "status": site.status, - "title": site.title, - "description": site.description, - "severity": site.severity, - "hazard_flag": site.hazard_flag, - "cleaned_at": site.cleaned_at.isoformat() if site.cleaned_at else None, - "created_at": site.created_at.isoformat(), - "can_mark_cleaned": can_mark_cleaned(user, site) if user else False, - }, + "properties": props, } + if site.area: + feature["properties"]["area_geojson"] = json.loads(site.area.geojson) + return feature -def _route_to_feature(route, user=None): - return { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [[coord[0], coord[1]] for coord in route.geometry.coords], - }, - "properties": { - "id": str(route.id), - "type": "route_cleanup", - "status": route.status, - "notes": route.notes, - "distance_miles": route.distance_miles, - "time_spent_minutes": route.time_spent_minutes, - "created_at": route.created_at.isoformat(), - "is_admin": is_admin(user) if user else False, - }, - } +# --------------------------------------------------------------------------- +# HTML views +# --------------------------------------------------------------------------- +def map_view(request): + return render(request, "geoapp/map.html") -@login_required -def home(request): - return redirect("map") +def cleanups_view(request): + sites = ( + TrashSite.objects.filter(status=TrashSite.Status.CLEANED) + .select_related("created_by") + .prefetch_related("proofs__photos", "proofs__created_by") + .order_by("-cleaned_at") + ) + paginator = Paginator(sites, 12) + page_obj = paginator.get_page(request.GET.get("page", 1)) -@login_required -def map_view(request): - return render(request, "geoapp/map.html") + items = [] + for site in page_obj: + photos = _photo_urls_grouped(site.proofs.all()) + items.append({ + "id": str(site.id), + "title": site.title or "Trash Site", + "description": site.description, + "severity": site.severity, + "cleaned_at": site.cleaned_at, + "created_by": site.created_by.username, + "bags_total": sum(p.bags_count for p in site.proofs.all()), + "photos": photos, + }) + + return render(request, "geoapp/cleanups.html", { + "items": items, + "page_obj": page_obj, + }) @require_GET @@ -229,62 +234,159 @@ def healthz(request): return JsonResponse({"ok": True, "database": "up"}) -@login_required -def updates_view(request): - activities = ActivityLog.objects.select_related("actor", "trash_site", "route_cleanup")[:25] - context = { - "activities": [_serialize_activity(activity) for activity in activities], - } - return render(request, "geoapp/updates.html", context) - +def signup_view(request): + if request.user.is_authenticated: + return redirect("/") + if request.method == "POST": + form = UserCreationForm(request.POST) + if form.is_valid(): + user = form.save() + login(request, user) + return redirect("/") + else: + form = UserCreationForm() + return render(request, "registration/signup.html", {"form": form}) -@login_required -def impact_view(request): - return render(request, "geoapp/impact.html", {"stats": build_user_impact_stats(request.user)}) +# --------------------------------------------------------------------------- +# JSON API — public +# --------------------------------------------------------------------------- -@login_required +@ratelimit(key="ip", rate="120/m", method="GET", block=True) @require_GET def features_api(request): bbox = _parse_bbox(request.GET.get("bbox", "")) status_filters = _status_choices(request.GET.get("status", "")) cutoff = _days_to_cutoff(request.GET.get("days", "all")) + district_slug = request.GET.get("district", "").strip() - trash_qs = TrashSite.objects.select_related("created_by") - route_qs = RouteCleanup.objects.select_related("created_by") + trash_qs = TrashSite.objects.select_related("created_by").prefetch_related("proofs__photos") if bbox: trash_qs = trash_qs.filter(location__within=bbox) - route_qs = route_qs.filter(geometry__intersects=bbox) if status_filters: trash_qs = trash_qs.filter(status__in=status_filters) if cutoff: trash_qs = trash_qs.filter(created_at__gte=cutoff) - route_qs = route_qs.filter(created_at__gte=cutoff) + if district_slug: + trash_qs = trash_qs.filter(district__slug=district_slug) - features = [_site_to_feature(site, user=request.user) for site in trash_qs] + [ - _route_to_feature(route, user=request.user) for route in route_qs - ] + user = request.user if request.user.is_authenticated else None + features = [_site_to_feature(site, user=user) for site in trash_qs] return JsonResponse({"type": "FeatureCollection", "features": features}) +@ratelimit(key="ip", rate="30/m", method="GET", block=True) +@require_GET +def districts_api(request): + districts = District.objects.filter(active=True) + results = [] + for d in districts: + results.append({ + "id": str(d.id), + "name": d.name, + "slug": d.slug, + "description": d.description, + "geometry": json.loads(d.geometry.geojson), + }) + return JsonResponse({"districts": results}) + + +@require_GET +def trash_site_detail_api(request, site_id): + site = get_object_or_404( + TrashSite.objects.select_related("created_by", "claimed_by").prefetch_related( + "proofs__photos", "proofs__created_by" + ), + id=site_id, + ) + user = request.user if request.user.is_authenticated else None + return JsonResponse(_serialize_trash_site(site, user=user)) + + +@ratelimit(key="ip", rate="60/m", method="GET", block=True) +@require_GET +def cleanups_list_api(request): + page_number = request.GET.get("page", "1") + page_size_raw = request.GET.get("page_size", "20") + + sites = ( + TrashSite.objects.filter(status=TrashSite.Status.CLEANED) + .select_related("created_by") + .prefetch_related("proofs__photos", "proofs__created_by") + .order_by("-cleaned_at") + ) + + try: + page_size = max(1, min(int(page_size_raw), 50)) + except ValueError: + page_size = 20 + + paginator = Paginator(sites, page_size) + page_obj = paginator.get_page(page_number) + + results = [] + for site in page_obj: + photos = _photo_urls_grouped(site.proofs.all()) + results.append({ + "id": str(site.id), + "title": site.title, + "description": site.description, + "severity": site.severity, + "cleaned_at": site.cleaned_at.isoformat() if site.cleaned_at else None, + "created_by": site.created_by.username, + "bags_total": sum(p.bags_count for p in site.proofs.all()), + "photos": photos, + }) + + return JsonResponse({ + "count": paginator.count, + "page": page_obj.number, + "num_pages": paginator.num_pages, + "results": results, + }) + + +# --------------------------------------------------------------------------- +# JSON API — authenticated +# --------------------------------------------------------------------------- + @login_required +@ratelimit(key="user", rate="10/h", method="POST", block=True) @require_http_methods(["POST"]) def trash_site_create_api(request): data = _load_payload(request) - geojson = data.get("geojson") + geojson_raw = data.get("geojson") point = None + area = None - if geojson: - if isinstance(geojson, str): + if geojson_raw: + if isinstance(geojson_raw, str): try: - geojson = json.loads(geojson) + geojson_raw = json.loads(geojson_raw) except json.JSONDecodeError: return _json_error("Invalid GeoJSON payload.") - coords = geojson.get("coordinates", []) - if len(coords) != 2: - return _json_error("Invalid GeoJSON coordinates.") - point = Point(float(coords[0]), float(coords[1]), srid=4326) + + geom_type = geojson_raw.get("type", "") + + if geom_type == "Point": + coords = geojson_raw.get("coordinates", []) + if len(coords) != 2: + return _json_error("Invalid Point coordinates.") + point = Point(float(coords[0]), float(coords[1]), srid=4326) + + elif geom_type == "Polygon": + try: + area = GEOSGeometry(json.dumps(geojson_raw), srid=4326) + if not isinstance(area, Polygon): + return _json_error("Invalid Polygon geometry.") + point = area.centroid + point.srid = 4326 + except Exception: + return _json_error("Invalid Polygon geometry.") + + else: + return _json_error("geojson type must be Point or Polygon.") else: try: lat = float(data.get("lat")) @@ -297,8 +399,18 @@ def trash_site_create_api(request): if severity and severity not in TrashSite.Severity.values: return _json_error("Invalid severity.") + photos = request.FILES.getlist("photos") + try: + validate_photo_uploads(photos) + except ValidationError as exc: + return _json_error(str(exc.message)) + + district = assign_district(point) + site = TrashSite.objects.create( location=point, + area=area, + district=district, title=data.get("title", "").strip(), description=data.get("description", "").strip(), severity=severity, @@ -306,7 +418,6 @@ def trash_site_create_api(request): created_by=request.user, ) - photos = request.FILES.getlist("photos") if photos: proof = CleanupProof.objects.create( trash_site=site, @@ -315,7 +426,7 @@ def trash_site_create_api(request): created_by=request.user, ) for image in photos: - Photo.objects.create(proof=proof, image=image) + Photo.objects.create(proof=proof, image=image, photo_type=Photo.PhotoType.REPORT) log_activity( ActivityLog.ActivityType.PROOF_ADDED, request.user, @@ -338,6 +449,7 @@ def trash_site_create_api(request): @require_http_methods(["GET", "PATCH"]) def trash_site_update_api(request, site_id): site = get_object_or_404(TrashSite, id=site_id) + if request.method == "GET": return JsonResponse(_serialize_trash_site(site, user=request.user)) @@ -370,6 +482,7 @@ def trash_site_update_api(request, site_id): if site.status != TrashSite.Status.CLEANED: site.cleaned_at = None site.save() + log_activity( ActivityLog.ActivityType.TRASH_UPDATED, request.user, @@ -380,6 +493,7 @@ def trash_site_update_api(request, site_id): @login_required +@ratelimit(key="user", rate="10/h", method="POST", block=True) @require_http_methods(["POST"]) def trash_site_mark_cleaned_api(request, site_id): site = get_object_or_404(TrashSite, id=site_id) @@ -388,7 +502,14 @@ def trash_site_mark_cleaned_api(request, site_id): note = request.POST.get("note", "").strip() bags_count = _coerce_int(request.POST.get("bags_count"), default=0) - photos = request.FILES.getlist("photos") + before_photos = request.FILES.getlist("before_photos") + after_photos = request.FILES.getlist("after_photos") + + try: + validate_photo_uploads(before_photos) + validate_photo_uploads(after_photos) + except ValidationError as exc: + return _json_error(str(exc.message)) proof = CleanupProof.objects.create( trash_site=site, @@ -396,8 +517,10 @@ def trash_site_mark_cleaned_api(request, site_id): bags_count=max(0, bags_count), created_by=request.user, ) - for image in photos: - Photo.objects.create(proof=proof, image=image) + for image in before_photos: + Photo.objects.create(proof=proof, image=image, photo_type=Photo.PhotoType.BEFORE) + for image in after_photos: + Photo.objects.create(proof=proof, image=image, photo_type=Photo.PhotoType.AFTER) site.status = TrashSite.Status.CLEANED site.cleaned_at = timezone.now() @@ -410,142 +533,12 @@ def trash_site_mark_cleaned_api(request, site_id): proof=proof, summary=f"{request.user.username} marked a trash site cleaned.", ) - if photos: - log_activity( - ActivityLog.ActivityType.PROOF_ADDED, - request.user, - trash_site=site, - proof=proof, - summary=f"{request.user.username} attached cleanup proof photos.", - ) return JsonResponse(_serialize_trash_site(site, user=request.user)) -def _extract_route_coordinates(data): - geojson = data.get("geojson") - if geojson: - if isinstance(geojson, str): - geojson = json.loads(geojson) - if geojson.get("type") != "LineString": - raise ValueError("geojson must be a LineString") - coords = geojson.get("coordinates", []) - else: - coords = data.get("coordinates", []) - if isinstance(coords, str): - coords = json.loads(coords) - - if not isinstance(coords, list) or len(coords) < 2: - raise ValueError("At least 2 coordinates are required.") - - parsed = [] - for coord in coords: - if not isinstance(coord, (list, tuple)) or len(coord) < 2: - raise ValueError("Coordinates must be [lng, lat].") - parsed.append((float(coord[0]), float(coord[1]))) - return parsed - - -@login_required -@require_http_methods(["POST"]) -def route_cleanup_create_api(request): - data = _load_payload(request) - try: - coords = _extract_route_coordinates(data) - except (ValueError, TypeError, json.JSONDecodeError) as exc: - return _json_error(str(exc)) - - time_spent = _coerce_int(data.get("time_spent_minutes"), default=None) - if time_spent is not None and time_spent < 0: - return _json_error("time_spent_minutes must be non-negative.") - - route = RouteCleanup.objects.create( - geometry=LineString(*coords, srid=4326), - notes=str(data.get("notes", "")).strip(), - time_spent_minutes=time_spent, - created_by=request.user, - ) - - photos = request.FILES.getlist("photos") - if photos: - proof = CleanupProof.objects.create( - route_cleanup=route, - note=route.notes, - bags_count=max(0, _coerce_int(data.get("bags_count"), default=0)), - created_by=request.user, - ) - for image in photos: - Photo.objects.create(proof=proof, image=image) - log_activity( - ActivityLog.ActivityType.PROOF_ADDED, - request.user, - route_cleanup=route, - proof=proof, - summary=f"{request.user.username} attached route cleanup photos.", - ) - - log_activity( - ActivityLog.ActivityType.ROUTE_LOGGED, - request.user, - route_cleanup=route, - summary=f"{request.user.username} logged a cleanup route.", - ) - - return JsonResponse(_serialize_route(route, user=request.user), status=201) - - -@login_required -@require_GET -def trash_site_detail_api(request, site_id): - site = get_object_or_404( - TrashSite.objects.select_related("created_by", "claimed_by").prefetch_related("proofs__photos", "proofs__created_by"), - id=site_id, - ) - return JsonResponse(_serialize_trash_site(site, user=request.user)) - - -@login_required -@require_GET -def route_cleanup_detail_api(request, route_id): - route = get_object_or_404( - RouteCleanup.objects.select_related("created_by").prefetch_related("proofs__photos", "proofs__created_by"), - id=route_id, - ) - return JsonResponse(_serialize_route(route, user=request.user)) - - -@login_required -@require_GET -def activity_api(request): - cutoff = _days_to_cutoff(request.GET.get("days", "all")) - activity_type = str(request.GET.get("type", "")).upper().strip() - page_number = request.GET.get("page", "1") - page_size_raw = request.GET.get("page_size", "25") - - activities = ActivityLog.objects.select_related("actor", "trash_site", "route_cleanup") - if cutoff: - activities = activities.filter(created_at__gte=cutoff) - if activity_type and activity_type in ActivityLog.ActivityType.values: - activities = activities.filter(activity_type=activity_type) - - try: - page_size = max(1, min(int(page_size_raw), 100)) - except ValueError: - page_size = 25 - - paginator = Paginator(activities, page_size) - page_obj = paginator.get_page(page_number) - return JsonResponse( - { - "count": paginator.count, - "page": page_obj.number, - "num_pages": paginator.num_pages, - "results": [_serialize_activity(activity) for activity in page_obj.object_list], - } - ) - - @login_required +@ratelimit(key="user", rate="5/h", method="POST", block=True) @require_http_methods(["POST"]) def feedback_create_api(request): data = _load_payload(request) diff --git a/images/Demo_1.png b/images/Demo_1.png new file mode 100644 index 0000000..fc54e63 Binary files /dev/null and b/images/Demo_1.png differ diff --git a/images/Demo_2.png b/images/Demo_2.png new file mode 100644 index 0000000..6a08ec3 Binary files /dev/null and b/images/Demo_2.png differ diff --git a/images/Demo_3.png b/images/Demo_3.png new file mode 100644 index 0000000..e64571d Binary files /dev/null and b/images/Demo_3.png differ diff --git a/images/Demo_4.png b/images/Demo_4.png new file mode 100644 index 0000000..d4f45cd Binary files /dev/null and b/images/Demo_4.png differ diff --git a/images/UC_CleanUp-site-favicon.png b/images/UC_CleanUp-site-favicon.png new file mode 100644 index 0000000..565e373 Binary files /dev/null and b/images/UC_CleanUp-site-favicon.png differ diff --git a/putnam_trashmap/settings.py b/putnam_trashmap/settings.py index 537c12a..83852c9 100644 --- a/putnam_trashmap/settings.py +++ b/putnam_trashmap/settings.py @@ -32,20 +32,21 @@ def _env_list(name, default=""): "storages", "geoapp.apps.GeoappConfig", ] - + MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", - "django.middleware.csrf.CsrfViewMiddleware", - "django.contrib.auth.middleware.AuthenticationMiddleware", - "django.contrib.messages.middleware.MessageMiddleware", - "django.middleware.clickjacking.XFrameOptionsMiddleware", -] - -ROOT_URLCONF = "putnam_trashmap.urls" - + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "geoapp.middleware.IPBanMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "putnam_trashmap.urls" + TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", @@ -55,18 +56,18 @@ def _env_list(name, default=""): "context_processors": [ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", - "django.contrib.messages.context_processors.messages", - ], - }, - }, -] - -WSGI_APPLICATION = "putnam_trashmap.wsgi.application" - - -# Database -# https://docs.djangoproject.com/en/5.2/ref/settings/#databases - + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "putnam_trashmap.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/5.2/ref/settings/#databases + default_database_url = ( f"postgres://{os.getenv('POSTGRES_USER', 'putnam')}:{os.getenv('POSTGRES_PASSWORD', 'putnam')}" f"@{os.getenv('POSTGRES_HOST', '127.0.0.1')}:{os.getenv('POSTGRES_PORT', '5432')}" @@ -81,41 +82,58 @@ def _env_list(name, default=""): ssl_require=os.getenv("DATABASE_SSL_REQUIRE", "0") == "1", ) } - - -# Password validation -# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", - }, -] - - -# Internationalization -# https://docs.djangoproject.com/en/5.2/topics/i18n/ - + + +# Cache (used by django-ratelimit) + +CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "ratelimit", + } +} + +_redis_url = os.getenv("REDIS_URL") +if _redis_url: + CACHES["default"] = { + "BACKEND": "django.core.cache.backends.redis.RedisCache", + "LOCATION": _redis_url, + } + + +# Password validation +# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.2/topics/i18n/ + LANGUAGE_CODE = "en-us" TIME_ZONE = os.getenv("TIME_ZONE", "America/Chicago") USE_I18N = True USE_TZ = True - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/5.2/howto/static-files/ - + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.2/howto/static-files/ + STATIC_URL = "/static/" STATICFILES_DIRS = [BASE_DIR / "static"] STATIC_ROOT = BASE_DIR / "staticfiles" @@ -128,8 +146,8 @@ def _env_list(name, default=""): ) LOGIN_URL = "/accounts/login/" -LOGIN_REDIRECT_URL = "/map/" -LOGOUT_REDIRECT_URL = "/accounts/login/" +LOGIN_REDIRECT_URL = "/" +LOGOUT_REDIRECT_URL = "/" USE_S3_MEDIA = os.getenv("USE_S3_MEDIA", "0") == "1" if USE_S3_MEDIA: @@ -167,6 +185,9 @@ def _env_list(name, default=""): }, } +# Required by OSM tile usage policy — send origin on cross-origin tile requests. +SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin" + if not DEBUG: SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") SECURE_SSL_REDIRECT = os.getenv("SECURE_SSL_REDIRECT", "1") == "1" diff --git a/putnam_trashmap/urls.py b/putnam_trashmap/urls.py index 4dce996..bfd3875 100644 --- a/putnam_trashmap/urls.py +++ b/putnam_trashmap/urls.py @@ -3,8 +3,11 @@ from django.conf.urls.static import static from django.urls import include, path +from geoapp.views import signup_view + urlpatterns = [ path("admin/", admin.site.urls), + path("accounts/signup/", signup_view, name="signup"), path("accounts/", include("django.contrib.auth.urls")), path("", include("geoapp.urls")), ] diff --git a/requirements.txt b/requirements.txt index 5b6639f..a69bf45 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ whitenoise>=6.8,<7.0 dj-database-url>=2.2,<3.0 django-storages>=1.14,<2.0 boto3>=1.35,<2.0 +django-ratelimit>=4.1,<5.0 diff --git a/static/css/map.css b/static/css/map.css index 9ff837f..a5bd1d3 100644 --- a/static/css/map.css +++ b/static/css/map.css @@ -358,35 +358,71 @@ box-shadow: var(--shadow-sm); } -.map-type-footer { - margin-top: 0; +.mode-switcher-section { + padding: 1rem; } -.overlay-toggle-row { - margin-bottom: 0; +.mode-switcher { + display: flex; + gap: 0.5rem; + margin-bottom: 0.75rem; } -.overlay-mode-list { - display: flex; - flex-direction: column; - gap: 0.38rem; +.btn-mode { + flex: 1; + min-height: 2.85rem; + border-radius: var(--radius-pill); + font-weight: 800; + transition: background-color 0.18s ease, color 0.18s ease, transform 0.18s ease; } -.county-boundary-wrap { - margin-top: 0.55rem; +.btn-mode--report { + background: var(--color-btn-subtle-bg); + color: var(--color-btn-subtle-text); } -.county-boundary-list { - display: flex; - flex-direction: column; - gap: 0.38rem; - max-height: 220px; - overflow-y: auto; - overflow-x: hidden; - padding: 0.75rem; - border: 1px solid var(--color-border-soft); - border-radius: var(--radius-sm); - background: var(--color-surface-soft); +.btn-mode--report.is-active { + background: var(--color-mode-report-bg); + color: #fff; +} + +.btn-mode--report:hover { + background: var(--color-mode-report-bg-hover); + color: #fff; +} + +.btn-mode--cleanup { + background: var(--color-btn-subtle-bg); + color: var(--color-btn-subtle-text); +} + +.btn-mode--cleanup.is-active { + background: var(--color-mode-cleanup-bg); + color: #fff; +} + +.btn-mode--cleanup:hover { + background: var(--color-mode-cleanup-bg-hover); + color: #fff; +} + +.mode-instructions { + min-height: 1.4rem; + color: var(--color-text-secondary); + font-size: 0.92rem; +} + +.report-mode-panel h2, +.cleanup-mode-panel h2 { + margin-bottom: 0.3rem; + font-size: 1rem; +} + +.report-mode-panel p, +.cleanup-mode-panel p { + margin: 0 0 0.75rem; + color: var(--color-text-secondary); + font-size: 0.92rem; } .checkbox-label { @@ -403,8 +439,8 @@ margin: 0; } -.is-hidden { - display: none; +.hidden { + display: none !important; } #map { @@ -540,3 +576,20 @@ min-height: 50vh; } } + +/* District label tooltip */ +.district-label { + background: rgba(14, 94, 168, 0.85); + color: #fff; + border: none; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25); + font-weight: 700; + font-size: 0.75rem; + letter-spacing: 0.03em; + padding: 3px 8px; + border-radius: 3px; +} + +.district-label::before { + display: none; +} diff --git a/static/css/palette.css b/static/css/palette.css index 64c328d..dac5f3a 100644 --- a/static/css/palette.css +++ b/static/css/palette.css @@ -1,7 +1,7 @@ :root { --color-text-primary: #17324b; --color-text-secondary: #4d667c; - --color-text-muted: #6d8498; + --color-text-muted: #5a7489; /* darkened from #6d8498 for WCAG AA 4.5:1 */ --color-text-inverse: #f8fbff; --color-bg-page: #eef4f7; @@ -70,6 +70,23 @@ --shadow-md: 0 18px 42px rgba(12, 42, 66, 0.1); --shadow-lg: 0 28px 60px rgba(12, 42, 66, 0.14); + --color-toast-success-bg: #e7f6ee; + --color-toast-success-text: #1e7b48; + --color-toast-success-border: #b4dfc9; + --color-toast-error-bg: #fbe6e9; + --color-toast-error-text: #b6364d; + --color-toast-error-border: #f0bfc7; + --color-toast-info-bg: #edf7ff; + --color-toast-info-text: #216ea3; + --color-toast-info-border: #b5d6f0; + + --color-mode-report-bg: #0d5d95; + --color-mode-report-bg-hover: #094b77; + --color-mode-cleanup-bg: #1f7a5c; + --color-mode-cleanup-bg-hover: #175f48; + + --color-district-accent: #0e5ea8; + --map-marker-pending: #db5a26; --map-marker-in-progress: #ccb10f; --map-marker-cleaned: #1f944f; diff --git a/static/css/site.css b/static/css/site.css index 20ebd80..b5e0c06 100644 --- a/static/css/site.css +++ b/static/css/site.css @@ -167,6 +167,245 @@ button:disabled, font-size: 0.94rem; } +.skip-link { + position: absolute; + left: -9999px; + top: auto; + z-index: 9999; + padding: 0.75rem 1rem; + background: var(--color-surface); + color: var(--color-btn-primary-bg); + font-weight: 700; + border-radius: var(--radius-sm); + text-decoration: none; +} + +.skip-link:focus { + position: fixed; + left: 1rem; + top: 1rem; + box-shadow: var(--shadow-md); +} + +.toast-container { + position: fixed; + top: 5rem; + right: 1rem; + z-index: 2000; + display: flex; + flex-direction: column; + gap: 0.5rem; + pointer-events: none; +} + +.toast { + pointer-events: auto; + display: flex; + align-items: center; + gap: 0.6rem; + min-width: 280px; + max-width: 420px; + padding: 0.85rem 1rem; + border-radius: var(--radius-md); + font-weight: 600; + font-size: 0.95rem; + box-shadow: var(--shadow-md); + animation: toast-slide-in 0.3s ease; +} + +.toast--success { + background: var(--color-toast-success-bg); + color: var(--color-toast-success-text); + border: 1px solid var(--color-toast-success-border); +} + +.toast--error { + background: var(--color-toast-error-bg); + color: var(--color-toast-error-text); + border: 1px solid var(--color-toast-error-border); +} + +.toast--info { + background: var(--color-toast-info-bg); + color: var(--color-toast-info-text); + border: 1px solid var(--color-toast-info-border); +} + +@keyframes toast-slide-in { + from { transform: translateX(100%); opacity: 0; } + to { transform: translateX(0); opacity: 1; } +} + +.auth-gate { + position: fixed; + inset: 0; + z-index: 1600; + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; + background: var(--color-overlay); +} + +.auth-gate.hidden { + display: none; +} + +.auth-gate-card { + text-align: center; + max-width: 400px; + padding: 2rem 1.5rem; + background: var(--color-surface); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-lg); +} + +.auth-gate-card h2 { + margin-bottom: 0.5rem; +} + +.auth-gate-card p { + margin: 0 0 1.25rem; + color: var(--color-text-secondary); +} + +.auth-gate-card .button-row { + justify-content: center; + margin-bottom: 0.75rem; +} + +.photo-upload-preview { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.photo-upload-preview img { + width: 72px; + height: 72px; + object-fit: cover; + border-radius: var(--radius-sm); + border: 1px solid var(--color-border-soft); +} + +.loading-spinner { + display: inline-block; + width: 1em; + height: 1em; + border: 2px solid rgba(255, 255, 255, 0.3); + border-top-color: #fff; + border-radius: 50%; + animation: spin 0.6s linear infinite; +} + +.loading-spinner.hidden { + display: none; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.cleanup-grid { + display: grid; + gap: 1.25rem; +} + +.cleanup-card { + padding: 1.25rem; + background: rgba(255, 255, 255, 0.94); + border: 1px solid var(--color-border-soft); + border-radius: var(--radius-md); + box-shadow: var(--shadow-sm); +} + +.cleanup-card-header { + display: flex; + align-items: center; + gap: 0.6rem; + flex-wrap: wrap; + margin-bottom: 0.5rem; +} + +.cleanup-card-title { + margin: 0; + font-size: 1.15rem; +} + +.cleanup-card-desc { + margin: 0 0 0.75rem; + color: var(--color-text-secondary); +} + +.cleanup-card-meta { + display: flex; + flex-wrap: wrap; + gap: 0.5rem 1rem; + margin-bottom: 0.75rem; + color: var(--color-text-muted); + font-size: 0.92rem; +} + +.before-after-gallery { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + margin-top: 0.75rem; +} + +.gallery-column { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.gallery-label { + margin: 0; + font-size: 0.82rem; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--color-text-muted); +} + +.gallery-photos { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.gallery-photos img { + width: 100px; + height: 100px; + object-fit: cover; + border-radius: var(--radius-sm); + border: 1px solid var(--color-border-soft); +} + +.pagination { + display: flex; + align-items: center; + justify-content: center; + gap: 1rem; + margin-top: 1.5rem; +} + +.pagination-info { + color: var(--color-text-muted); + font-weight: 600; +} + +.auth-link { + margin-top: 1rem; + color: var(--color-text-secondary); + font-size: 0.95rem; +} + +.auth-link a { + color: var(--color-btn-primary-bg); + font-weight: 700; +} + .page { flex: 1; min-height: 0; @@ -182,20 +421,51 @@ button:disabled, } .topbar-inner { - width: min(100%, 1440px); + width: min(100%, 1920px); margin: 0 auto; display: flex; align-items: center; - justify-content: space-between; - gap: var(--space-4); + gap: 1.4rem; padding: 0.9rem 1.25rem; } -.topbar-left { - min-width: 0; +/* Hamburger — hidden on desktop */ +.hamburger { + display: none; + flex-direction: column; + justify-content: center; + gap: 4px; + width: 36px; + height: 36px; + padding: 6px; + border: none; + border-radius: var(--radius-sm); + background: rgba(255, 255, 255, 0.1); + cursor: pointer; +} + +.hamburger:hover { background: rgba(255, 255, 255, 0.18); } + +.hamburger-bar { + display: block; + width: 100%; + height: 2.5px; + border-radius: 2px; + background: #fff; + transition: transform 0.25s ease, opacity 0.2s ease; +} + +.hamburger[aria-expanded="true"] .hamburger-bar:nth-child(1) { transform: translateY(6.5px) rotate(45deg); } +.hamburger[aria-expanded="true"] .hamburger-bar:nth-child(2) { opacity: 0; } +.hamburger[aria-expanded="true"] .hamburger-bar:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); } + +/* Desktop: nav-collapse sits inline between brand and right edge */ +.nav-collapse { display: flex; align-items: center; - gap: 1.4rem; + flex: 1; + justify-content: space-between; + gap: var(--space-4); } .brand { @@ -576,48 +846,45 @@ button:disabled, color: var(--color-text-secondary); } -@media (max-width: 1024px) { - .topbar-inner { - align-items: flex-start; - flex-direction: column; +@media (max-width: 900px) { + .hamburger { + display: flex; } - .topbar-left, - .user-actions { - width: 100%; + .topbar-inner { + flex-wrap: wrap; justify-content: space-between; } -} -@media (max-width: 720px) { - .topbar-inner { - gap: 0.9rem; - padding: 0.95rem 1rem 1rem; + .nav-collapse { + display: none; + flex-direction: column; + align-items: stretch; + width: 100%; + gap: 0.5rem; + padding-top: 0.75rem; } - .topbar-left, - .user-actions { - align-items: flex-start; - flex-direction: column; - gap: 0.75rem; + .nav-collapse.is-open { + display: flex; } .topnav { - gap: 0.35rem; + flex-direction: column; + gap: 0.25rem; } .topnav-link { - min-height: 2.15rem; - padding: 0.38rem 0.68rem; - } - - .user-actions { width: 100%; + border-radius: var(--radius-sm); } - .user-actions .btn, - .user-actions button { - width: auto; + .user-actions { + flex-direction: column; + align-items: stretch; + gap: 0.5rem; + padding-top: 0.5rem; + border-top: 1px solid rgba(255, 255, 255, 0.12); } .content-shell, diff --git a/static/data/district_3_boundary.geojson b/static/data/district_3_boundary.geojson new file mode 100644 index 0000000..797cfb1 --- /dev/null +++ b/static/data/district_3_boundary.geojson @@ -0,0 +1,2130 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "NAME": "Putnam County Commission District 3", + "DISTRICT": 3, + "source": "TNMap ArcGIS - CCD=3 dissolved" + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [ + -85.49892766819633, + 36.19904889941947 + ], + [ + -85.4989106682953, + 36.19919489889974 + ], + [ + -85.49928266810883, + 36.19919889887606 + ], + [ + -85.49928866818583, + 36.199164899092594 + ], + [ + -85.49931066841711, + 36.199063898680976 + ], + [ + -85.4993176684693, + 36.19903089891596 + ], + [ + -85.49962066777076, + 36.19912589872724 + ], + [ + -85.49963766826045, + 36.19913189936173 + ], + [ + -85.50010166795535, + 36.19914189907542 + ], + [ + -85.50023266850721, + 36.19914489904268 + ], + [ + -85.5005646681033, + 36.19907189935929 + ], + [ + -85.50058666865488, + 36.199067898708385 + ], + [ + -85.50063566902769, + 36.19905689890233 + ], + [ + -85.50085966827378, + 36.198954898501356 + ], + [ + -85.50111966827168, + 36.198852898626875 + ], + [ + -85.50095566831155, + 36.198578898850776 + ], + [ + -85.50085966817036, + 36.19841789854574 + ], + [ + -85.50066266892858, + 36.19807289888514 + ], + [ + -85.50018366883047, + 36.19781389841753 + ], + [ + -85.4999336680043, + 36.19756689839917 + ], + [ + -85.4998166686706, + 36.19727689893603 + ], + [ + -85.49980866776453, + 36.19725689872133 + ], + [ + -85.49978666838159, + 36.196681898295616 + ], + [ + -85.50009866840666, + 36.19672389875284 + ], + [ + -85.50039966823243, + 36.196761898226065 + ], + [ + -85.50202966865282, + 36.19696889808579 + ], + [ + -85.50219566921947, + 36.19698989843889 + ], + [ + -85.5022426685316, + 36.19699489853496 + ], + [ + -85.50282366870256, + 36.19706889882755 + ], + [ + -85.50285466924397, + 36.197055898044724 + ], + [ + -85.5029386690342, + 36.197025898072326 + ], + [ + -85.50319166874344, + 36.19693589831566 + ], + [ + -85.50327666941149, + 36.196906898101425 + ], + [ + -85.5047116700023, + 36.19633689779528 + ], + [ + -85.50894267059857, + 36.19465689796246 + ], + [ + -85.5090166701852, + 36.19462789763243 + ], + [ + -85.51045267134401, + 36.194058897511226 + ], + [ + -85.51052567075457, + 36.19414189709532 + ], + [ + -85.51054067151209, + 36.194153897597054 + ], + [ + -85.51063967092651, + 36.194231897856085 + ], + [ + -85.51076967070273, + 36.194300897643146 + ], + [ + -85.51088267149643, + 36.19434189795311 + ], + [ + -85.51100567073412, + 36.194387897298306 + ], + [ + -85.51116867143072, + 36.19444989784864 + ], + [ + -85.51141767098427, + 36.19454489739679 + ], + [ + -85.51149367187448, + 36.19453289751822 + ], + [ + -85.51167767141423, + 36.19453289739775 + ], + [ + -85.51185267141, + 36.1945178972489 + ], + [ + -85.51183267177544, + 36.19437989712682 + ], + [ + -85.51177267098224, + 36.19396789746315 + ], + [ + -85.51175367171409, + 36.19383089722649 + ], + [ + -85.51166167137869, + 36.193235897338866 + ], + [ + -85.51157767168485, + 36.19262689725753 + ], + [ + -85.51157067097185, + 36.19257189749426 + ], + [ + -85.51131867104353, + 36.190794896407475 + ], + [ + -85.51127467145018, + 36.190430897185195 + ], + [ + -85.51123467120752, + 36.19009689674165 + ], + [ + -85.51121167102382, + 36.18959289638915 + ], + [ + -85.51121967125536, + 36.189203896079945 + ], + [ + -85.51122967064899, + 36.18900289652821 + ], + [ + -85.51125067117701, + 36.188633896063564 + ], + [ + -85.5112876706721, + 36.187788895934446 + ], + [ + -85.5113006702997, + 36.18756589640457 + ], + [ + -85.51132767102449, + 36.18713289642651 + ], + [ + -85.51133767089807, + 36.18689689594677 + ], + [ + -85.5113486706785, + 36.186674895857195 + ], + [ + -85.51164167139689, + 36.18670789633541 + ], + [ + -85.51251967101147, + 36.18680889562243 + ], + [ + -85.51281367159734, + 36.18684289586949 + ], + [ + -85.51282167082447, + 36.18677789600798 + ], + [ + -85.51282467148746, + 36.18664989596897 + ], + [ + -85.51283967083234, + 36.18607189605084 + ], + [ + -85.51284567150152, + 36.18587989614642 + ], + [ + -85.51285167121968, + 36.18565889591059 + ], + [ + -85.51285667070071, + 36.18544489611515 + ], + [ + -85.51285967160354, + 36.185346895832424 + ], + [ + -85.51286767150982, + 36.185247895506855 + ], + [ + -85.51287467095068, + 36.18512189519283 + ], + [ + -85.51286767156165, + 36.18478689600676 + ], + [ + -85.51280367081786, + 36.18414689565165 + ], + [ + -85.51276067048501, + 36.18371489494073 + ], + [ + -85.51274567116585, + 36.18346289524073 + ], + [ + -85.51274267143101, + 36.18343289503128 + ], + [ + -85.51269967141697, + 36.182974895027684 + ], + [ + -85.51269167090857, + 36.182794895338745 + ], + [ + -85.51267067131892, + 36.182589894833185 + ], + [ + -85.51264367127212, + 36.18230989530383 + ], + [ + -85.51263867088416, + 36.18226789500503 + ], + [ + -85.51262667135236, + 36.182141895407575 + ], + [ + -85.51262367035689, + 36.18210089545166 + ], + [ + -85.51260767081888, + 36.1819218953476 + ], + [ + -85.51258467118066, + 36.18163989491072 + ], + [ + -85.51256167088044, + 36.18138589531241 + ], + [ + -85.51254667045428, + 36.18120789507722 + ], + [ + -85.51253967026425, + 36.18112789458267 + ], + [ + -85.5125256704546, + 36.18094989476737 + ], + [ + -85.51248567053835, + 36.18044189442546 + ], + [ + -85.51246967092843, + 36.18017689442266 + ], + [ + -85.51245567055973, + 36.179918894355815 + ], + [ + -85.51274367080605, + 36.17995589439432 + ], + [ + -85.51360767090574, + 36.180066894882295 + ], + [ + -85.51364667126093, + 36.18007189442071 + ], + [ + -85.51389667163566, + 36.180101894523446 + ], + [ + -85.51401967142843, + 36.18011689443845 + ], + [ + -85.51445367088998, + 36.1801718946749 + ], + [ + -85.51575867217215, + 36.18033889431291 + ], + [ + -85.51612367200431, + 36.1803998943316 + ], + [ + -85.51632367202173, + 36.18043389419318 + ], + [ + -85.51667467195408, + 36.18050989449615 + ], + [ + -85.51668167140087, + 36.18044889452278 + ], + [ + -85.51668967159709, + 36.18034989473489 + ], + [ + -85.5167546719942, + 36.179799894425614 + ], + [ + -85.51681967226628, + 36.17925889386585 + ], + [ + -85.51691067212947, + 36.17843089378663 + ], + [ + -85.51700267185227, + 36.1776718943432 + ], + [ + -85.51700967212551, + 36.17761889379091 + ], + [ + -85.51709367172104, + 36.17705789348031 + ], + [ + -85.51717067150197, + 36.177004893531986 + ], + [ + -85.51680767126547, + 36.176436893560236 + ], + [ + -85.51671267211425, + 36.1762878937172 + ], + [ + -85.51654467163272, + 36.17601689408324 + ], + [ + -85.51647567141409, + 36.1759208933601 + ], + [ + -85.51643067149918, + 36.1758408939839 + ], + [ + -85.51599567157947, + 36.175180893583104 + ], + [ + -85.51571167145826, + 36.174741893389054 + ], + [ + -85.51534667113614, + 36.17417789292994 + ], + [ + -85.5152756713259, + 36.17406289320472 + ], + [ + -85.51518667105269, + 36.17391889283105 + ], + [ + -85.51516367158631, + 36.173895893394885 + ], + [ + -85.51507967157247, + 36.173727892915004 + ], + [ + -85.51507567149146, + 36.17371689331702 + ], + [ + -85.51506467058661, + 36.17368589300211 + ], + [ + -85.51504967060697, + 36.17358589317357 + ], + [ + -85.51504967046662, + 36.173517892989125 + ], + [ + -85.5150696709086, + 36.17337989288482 + ], + [ + -85.51512567079021, + 36.172998892832254 + ], + [ + -85.51516567122718, + 36.172764892594806 + ], + [ + -85.51520167099477, + 36.17256089328961 + ], + [ + -85.51523167091136, + 36.17236189253277 + ], + [ + -85.51528567083423, + 36.17200689254671 + ], + [ + -85.51532767050979, + 36.171765893006175 + ], + [ + -85.51536267071708, + 36.171568893202064 + ], + [ + -85.51553167105408, + 36.171580893134355 + ], + [ + -85.51603867125463, + 36.17161689240798 + ], + [ + -85.51620867164999, + 36.17162989308488 + ], + [ + -85.51621867124021, + 36.171629892607115 + ], + [ + -85.51624967155303, + 36.17163189235271 + ], + [ + -85.51626067100362, + 36.17163289266112 + ], + [ + -85.5163746715262, + 36.17163889306709 + ], + [ + -85.51664367094453, + 36.171655892944734 + ], + [ + -85.51671967174804, + 36.17166189316196 + ], + [ + -85.51683467174215, + 36.17167189237597 + ], + [ + -85.51699167168059, + 36.17168889283027 + ], + [ + -85.51704067163374, + 36.171694892946526 + ], + [ + -85.51741467178361, + 36.17171789262382 + ], + [ + -85.51746367185245, + 36.17172589244213 + ], + [ + -85.51762067178235, + 36.17175189286007 + ], + [ + -85.51791267204601, + 36.17177789248162 + ], + [ + -85.51833767196037, + 36.17181689278614 + ], + [ + -85.51879067215818, + 36.17183689312094 + ], + [ + -85.51908467216889, + 36.17185089232716 + ], + [ + -85.51910767261057, + 36.171850892937385 + ], + [ + -85.51912167181042, + 36.17173989309862 + ], + [ + -85.5191456717091, + 36.17163989229396 + ], + [ + -85.51931767240383, + 36.17094789287717 + ], + [ + -85.51937567227178, + 36.1707178922976 + ], + [ + -85.51944367242817, + 36.17049889249615 + ], + [ + -85.51945167243561, + 36.17047389212344 + ], + [ + -85.51951267187953, + 36.170381892007676 + ], + [ + -85.51955867180965, + 36.17033289208821 + ], + [ + -85.51968067266274, + 36.17025589264966 + ], + [ + -85.51974867174158, + 36.1702298926812 + ], + [ + -85.51990167195252, + 36.17015289198226 + ], + [ + -85.51998767207348, + 36.1701158925199 + ], + [ + -85.52019967202091, + 36.17002789197579 + ], + [ + -85.52009467220633, + 36.16987289210535 + ], + [ + -85.5198636722796, + 36.16953189203901 + ], + [ + -85.51979267194999, + 36.16940189171859 + ], + [ + -85.51970367195315, + 36.16923789215492 + ], + [ + -85.5197146724792, + 36.1692338920172 + ], + [ + -85.51975167220377, + 36.16922189218473 + ], + [ + -85.51976367260451, + 36.16921889190265 + ], + [ + -85.51959167189112, + 36.16899489198807 + ], + [ + -85.51949167180472, + 36.16886489230205 + ], + [ + -85.51938567166394, + 36.16861989159166 + ], + [ + -85.51948967198288, + 36.1682198917166 + ], + [ + -85.51956167188077, + 36.16794789155863 + ], + [ + -85.51950567244774, + 36.16789989153618 + ], + [ + -85.51933867157283, + 36.16775589167471 + ], + [ + -85.51928367165017, + 36.167707891921125 + ], + [ + -85.51917867233367, + 36.16760589190073 + ], + [ + -85.51914667217032, + 36.16757489207054 + ], + [ + -85.5189786714645, + 36.167425892076004 + ], + [ + -85.51886167186173, + 36.16730889219028 + ], + [ + -85.51883867214549, + 36.167285891606475 + ], + [ + -85.51875767183616, + 36.16720889204282 + ], + [ + -85.51848467139891, + 36.16696289187785 + ], + [ + -85.51846467148057, + 36.16694489161184 + ], + [ + -85.51825367132595, + 36.1667668915961 + ], + [ + -85.51811267136227, + 36.166668891769234 + ], + [ + -85.51790467126345, + 36.166527891351755 + ], + [ + -85.51767367185225, + 36.16638489161261 + ], + [ + -85.51758167091398, + 36.166337891383364 + ], + [ + -85.51745267099342, + 36.16627389156987 + ], + [ + -85.51724567129222, + 36.16619189170501 + ], + [ + -85.51713267112297, + 36.16614789121778 + ], + [ + -85.517115670825, + 36.16614189206901 + ], + [ + -85.51703267123577, + 36.16611389155853 + ], + [ + -85.51671567111352, + 36.1660218918876 + ], + [ + -85.51658267052058, + 36.16598389153805 + ], + [ + -85.51657167069412, + 36.16598089137602 + ], + [ + -85.51653867121433, + 36.165971891785205 + ], + [ + -85.51652867060932, + 36.16596889201675 + ], + [ + -85.51647767118497, + 36.165954891361785 + ], + [ + -85.51645167075367, + 36.16594789172386 + ], + [ + -85.51632767127514, + 36.16591589163446 + ], + [ + -85.51627767117343, + 36.16590389117099 + ], + [ + -85.51603367123406, + 36.16584689123872 + ], + [ + -85.51598667058217, + 36.165837891231064 + ], + [ + -85.51569767016092, + 36.16578589144243 + ], + [ + -85.51510567087618, + 36.165692891900584 + ], + [ + -85.51481267078607, + 36.16564789196341 + ], + [ + -85.51480267084494, + 36.16564589154187 + ], + [ + -85.51477467000247, + 36.16564289136505 + ], + [ + -85.51476567094015, + 36.165641891748365 + ], + [ + -85.51464867040747, + 36.16562589184698 + ], + [ + -85.51446967023598, + 36.16559889140765 + ], + [ + -85.51381266981176, + 36.16550289141027 + ], + [ + -85.51358367015648, + 36.16546689201847 + ], + [ + -85.51328866944633, + 36.16542089163625 + ], + [ + -85.51314967028708, + 36.165398892058164 + ], + [ + -85.51289867028036, + 36.16535989157499 + ], + [ + -85.51287766988146, + 36.16532589181716 + ], + [ + -85.51275266992684, + 36.16530589137856 + ], + [ + -85.51272166947258, + 36.16530089135481 + ], + [ + -85.51271966940473, + 36.165299891903096 + ], + [ + -85.51261566959847, + 36.16528189126389 + ], + [ + -85.51255467016203, + 36.16527189157739 + ], + [ + -85.51241066984845, + 36.16524989167623 + ], + [ + -85.51212366911899, + 36.16520889206112 + ], + [ + -85.51179766949151, + 36.165152891304515 + ], + [ + -85.5115936691767, + 36.16511789139708 + ], + [ + -85.51151366910004, + 36.16510289165557 + ], + [ + -85.511275669603, + 36.16505989197997 + ], + [ + -85.51119666936376, + 36.165045891959565 + ], + [ + -85.5111066692477, + 36.165025891858704 + ], + [ + -85.51083666957275, + 36.164968891421964 + ], + [ + -85.5107466690245, + 36.164949891439335 + ], + [ + -85.51041066900704, + 36.16487989140105 + ], + [ + -85.50981566853885, + 36.16475589205298 + ], + [ + -85.50964766927177, + 36.16472089175442 + ], + [ + -85.50940366882955, + 36.164670891908024 + ], + [ + -85.50906766878703, + 36.1646028915993 + ], + [ + -85.50885466868712, + 36.164561891360215 + ], + [ + -85.50882366828102, + 36.16455589174499 + ], + [ + -85.5087326688363, + 36.16453789198684 + ], + [ + -85.50846666863711, + 36.16450289125527 + ], + [ + -85.50821366886314, + 36.16448089209977 + ], + [ + -85.50808566877743, + 36.16447389137292 + ], + [ + -85.50790766812524, + 36.16446589175385 + ], + [ + -85.50783966863766, + 36.16445789165753 + ], + [ + -85.50781466793481, + 36.16463989130401 + ], + [ + -85.50774166846368, + 36.165186891509556 + ], + [ + -85.50771766809129, + 36.16536989196673 + ], + [ + -85.50771066887269, + 36.16540989162152 + ], + [ + -85.50769166783938, + 36.16553089190475 + ], + [ + -85.50768666859913, + 36.165571891992606 + ], + [ + -85.50767166816216, + 36.165693891851724 + ], + [ + -85.50766466882945, + 36.16575289169142 + ], + [ + -85.50762566874084, + 36.16606289209678 + ], + [ + -85.50761066857895, + 36.16618589232356 + ], + [ + -85.50761066888498, + 36.16621289202416 + ], + [ + -85.50761066873444, + 36.16625089240126 + ], + [ + -85.50760566793022, + 36.16629589222688 + ], + [ + -85.50760266880019, + 36.16632389179581 + ], + [ + -85.5075746683628, + 36.166527892140074 + ], + [ + -85.50749166857413, + 36.16714189252787 + ], + [ + -85.50747366805896, + 36.1672768923404 + ], + [ + -85.5074576687994, + 36.16734589265586 + ], + [ + -85.50742466877803, + 36.16758989257885 + ], + [ + -85.5073416687516, + 36.16820589257317 + ], + [ + -85.507324668243, + 36.168321892625194 + ], + [ + -85.50729066861373, + 36.16856689236443 + ], + [ + -85.50726266815158, + 36.16877189290849 + ], + [ + -85.5072516682569, + 36.16885589237313 + ], + [ + -85.5072136680133, + 36.16916189222312 + ], + [ + -85.50718166814876, + 36.16938689227186 + ], + [ + -85.50715266830794, + 36.169592892660546 + ], + [ + -85.50713966899858, + 36.16969489272904 + ], + [ + -85.50712566868478, + 36.16978589254398 + ], + [ + -85.50704566820067, + 36.17032089261028 + ], + [ + -85.50703966884905, + 36.17036389291829 + ], + [ + -85.50701566809302, + 36.17055789275753 + ], + [ + -85.50684966797594, + 36.170533892989134 + ], + [ + -85.50680166830647, + 36.170526893328166 + ], + [ + -85.50634966821785, + 36.170490892691696 + ], + [ + -85.5061836677879, + 36.17047789267102 + ], + [ + -85.50614766784345, + 36.17075389311344 + ], + [ + -85.50608466856748, + 36.17124089289145 + ], + [ + -85.50604066780825, + 36.171583892817765 + ], + [ + -85.50603666795918, + 36.1716178932478 + ], + [ + -85.50600866855646, + 36.17176489338168 + ], + [ + -85.50600866782077, + 36.17186089367475 + ], + [ + -85.50598366817923, + 36.17200489319028 + ], + [ + -85.50593566850822, + 36.1720448933992 + ], + [ + -85.50592966775929, + 36.172088893116054 + ], + [ + -85.50590666837593, + 36.17229489329958 + ], + [ + -85.5058856685229, + 36.172432893306016 + ], + [ + -85.50588566777232, + 36.17246889374595 + ], + [ + -85.50586766856827, + 36.17263589383949 + ], + [ + -85.50584066865719, + 36.172794893628655 + ], + [ + -85.50583366803404, + 36.17284289349348 + ], + [ + -85.50580266843961, + 36.17309489311708 + ], + [ + -85.50579466874265, + 36.17318889319816 + ], + [ + -85.50577366847178, + 36.17332189391755 + ], + [ + -85.50576466886506, + 36.17338189384324 + ], + [ + -85.50571966845308, + 36.1737728935508 + ], + [ + -85.50568766844228, + 36.17396089393426 + ], + [ + -85.50568266828475, + 36.17400589359583 + ], + [ + -85.50567266821695, + 36.174131893344985 + ], + [ + -85.50565766804876, + 36.17423489344431 + ], + [ + -85.50564366804447, + 36.1743398939036 + ], + [ + -85.50560166825694, + 36.17465589384032 + ], + [ + -85.50558866787486, + 36.17476189425706 + ], + [ + -85.5055556688191, + 36.17500289386185 + ], + [ + -85.50545866821696, + 36.17572889376209 + ], + [ + -85.50543666863685, + 36.17589789425775 + ], + [ + -85.50543666788502, + 36.17593689402297 + ], + [ + -85.50542966840864, + 36.17597089382133 + ], + [ + -85.50541866890462, + 36.176054893870415 + ], + [ + -85.50539566879965, + 36.17623689437254 + ], + [ + -85.50538266842855, + 36.17630589393091 + ], + [ + -85.50537666796201, + 36.176346894215285 + ], + [ + -85.5053756688933, + 36.17639089452289 + ], + [ + -85.50535066890939, + 36.17655789452371 + ], + [ + -85.50533766865215, + 36.17665289421301 + ], + [ + -85.50532966891716, + 36.17674889404835 + ], + [ + -85.5052796679914, + 36.17705889461914 + ], + [ + -85.50525366823521, + 36.17722589407888 + ], + [ + -85.50501266796634, + 36.17720689462977 + ], + [ + -85.50471166879197, + 36.17718389393368 + ], + [ + -85.50458966789965, + 36.17719489414213 + ], + [ + -85.50452866785301, + 36.17720789394701 + ], + [ + -85.50429566807867, + 36.17725689451327 + ], + [ + -85.50426266800972, + 36.177264894572104 + ], + [ + -85.5041396679233, + 36.177297894221994 + ], + [ + -85.504101667666, + 36.17730589442594 + ], + [ + -85.5040626681852, + 36.177316894376276 + ], + [ + -85.50406366761179, + 36.177340894241915 + ], + [ + -85.50408266764471, + 36.177398894682966 + ], + [ + -85.5041226684847, + 36.177516894631296 + ], + [ + -85.50416466863132, + 36.177643894536544 + ], + [ + -85.50419266768178, + 36.17772589432891 + ], + [ + -85.50426966815078, + 36.17796589414719 + ], + [ + -85.50442066852787, + 36.17843689491265 + ], + [ + -85.5045036686866, + 36.17868689508357 + ], + [ + -85.5045186687964, + 36.17872989485083 + ], + [ + -85.50457566793531, + 36.178781894721325 + ], + [ + -85.50458966836413, + 36.1788318948813 + ], + [ + -85.50461266879046, + 36.17890789514772 + ], + [ + -85.50438666818913, + 36.17888489500463 + ], + [ + -85.50370866826599, + 36.178815894744055 + ], + [ + -85.50348366797232, + 36.17879389470263 + ], + [ + -85.50326066837822, + 36.17877089487773 + ], + [ + -85.50259166822661, + 36.17870489498188 + ], + [ + -85.50236966780339, + 36.1786828944068 + ], + [ + -85.50224666799323, + 36.17867089457158 + ], + [ + -85.50187966793615, + 36.17863589483813 + ], + [ + -85.50185066732219, + 36.17863389516134 + ], + [ + -85.50175866736339, + 36.17862189477177 + ], + [ + -85.50154966750576, + 36.17860089445808 + ], + [ + -85.50092266746248, + 36.178537894945364 + ], + [ + -85.50088166692463, + 36.17853389512038 + ], + [ + -85.50071366696685, + 36.17852289459676 + ], + [ + -85.5006836672518, + 36.17867189478285 + ], + [ + -85.50067266671675, + 36.178743895126615 + ], + [ + -85.50060666745775, + 36.17922889509758 + ], + [ + -85.50056966689694, + 36.17941289510548 + ], + [ + -85.50056866681528, + 36.17942289483007 + ], + [ + -85.5005386675195, + 36.1796368948135 + ], + [ + -85.50050766684976, + 36.179860895396445 + ], + [ + -85.5004156676553, + 36.180532894861265 + ], + [ + -85.50038566718042, + 36.18075789537993 + ], + [ + -85.50036666689012, + 36.180893895352995 + ], + [ + -85.50032466777714, + 36.18119689518964 + ], + [ + -85.50031466715072, + 36.181304895453636 + ], + [ + -85.50030966721194, + 36.1813688958376 + ], + [ + -85.50029366777636, + 36.181440895232505 + ], + [ + -85.5002746672049, + 36.181615895785605 + ], + [ + -85.50021966681415, + 36.18214289562029 + ], + [ + -85.50020266736219, + 36.18231889578732 + ], + [ + -85.50017666703896, + 36.18252089573545 + ], + [ + -85.50009866712573, + 36.18312589600305 + ], + [ + -85.50007266775738, + 36.18332889537426 + ], + [ + -85.50004666753469, + 36.18351489628991 + ], + [ + -85.49999666713163, + 36.18387889607085 + ], + [ + -85.49997266728839, + 36.18407289569125 + ], + [ + -85.49995066698641, + 36.18425989637872 + ], + [ + -85.49991366756639, + 36.18449689562263 + ], + [ + -85.49982066734783, + 36.18509589584409 + ], + [ + -85.49980566745103, + 36.185210895960594 + ], + [ + -85.49977566748136, + 36.18544989603341 + ], + [ + -85.49976266737946, + 36.18544889585753 + ], + [ + -85.49972666694528, + 36.185446896421055 + ], + [ + -85.4997146677874, + 36.18544689626615 + ], + [ + -85.4994956676683, + 36.185424896645564 + ], + [ + -85.49943666691159, + 36.185424896264976 + ], + [ + -85.4993586673956, + 36.185424896380624 + ], + [ + -85.49860266734844, + 36.18536189628651 + ], + [ + -85.4983256665294, + 36.18533989607489 + ], + [ + -85.49822766681254, + 36.18533389655895 + ], + [ + -85.49803066709914, + 36.185312895975194 + ], + [ + -85.49792066658611, + 36.18530189665939 + ], + [ + -85.49714766712032, + 36.18523989643777 + ], + [ + -85.49706166674177, + 36.18523389638515 + ], + [ + -85.4968536659892, + 36.18524089667489 + ], + [ + -85.49683266616569, + 36.18546789649871 + ], + [ + -85.4967996665667, + 36.185835896022326 + ], + [ + -85.49676166600518, + 36.186064896904135 + ], + [ + -85.49673766620242, + 36.186145896507206 + ], + [ + -85.49670866601575, + 36.18625189658822 + ], + [ + -85.49665466656224, + 36.18635789697825 + ], + [ + -85.49669266628457, + 36.18637689687933 + ], + [ + -85.49675566661965, + 36.18645789684567 + ], + [ + -85.49678766629827, + 36.18651789648108 + ], + [ + -85.4967926663194, + 36.18652589696508 + ], + [ + -85.49704566681355, + 36.187105897102654 + ], + [ + -85.49705966657345, + 36.18713689704027 + ], + [ + -85.49713566658092, + 36.187300896314035 + ], + [ + -85.49722766720771, + 36.187506897189515 + ], + [ + -85.4974326666945, + 36.18796389703586 + ], + [ + -85.4974716667339, + 36.18805189669843 + ], + [ + -85.49750666713918, + 36.18812489647044 + ], + [ + -85.49758566727319, + 36.18828489734718 + ], + [ + -85.4976006664663, + 36.188330897214556 + ], + [ + -85.49776066715607, + 36.18867789671994 + ], + [ + -85.49785266716377, + 36.18887589665877 + ], + [ + -85.49787766743833, + 36.18895489666807 + ], + [ + -85.4979216672902, + 36.1891538973557 + ], + [ + -85.4979216672836, + 36.18945289670499 + ], + [ + -85.49788066678465, + 36.18979389754181 + ], + [ + -85.49784466728448, + 36.1900968973544 + ], + [ + -85.49782966695743, + 36.190172897449855 + ], + [ + -85.49780766717373, + 36.19036489757776 + ], + [ + -85.49779966696792, + 36.190447897019425 + ], + [ + -85.49777666756631, + 36.190649897703835 + ], + [ + -85.49777666710015, + 36.19094589758308 + ], + [ + -85.49777666742797, + 36.1910448976672 + ], + [ + -85.49778266696141, + 36.19113989773845 + ], + [ + -85.49780066722286, + 36.191458897447376 + ], + [ + -85.49780666749844, + 36.19155589737422 + ], + [ + -85.4977996669775, + 36.191740897653645 + ], + [ + -85.49777666700479, + 36.19191289759233 + ], + [ + -85.49776166774838, + 36.191976897558675 + ], + [ + -85.49771566668053, + 36.19223689767059 + ], + [ + -85.49767566771439, + 36.192406898182355 + ], + [ + -85.4976546673364, + 36.192502898026014 + ], + [ + -85.49763166769878, + 36.192624897694024 + ], + [ + -85.49760766752608, + 36.192694897622985 + ], + [ + -85.49759366678869, + 36.19271389800736 + ], + [ + -85.4975596677486, + 36.19275789773894 + ], + [ + -85.4974856672165, + 36.19281789752448 + ], + [ + -85.4974846676546, + 36.192817897870505 + ], + [ + -85.49733266674042, + 36.192900898034864 + ], + [ + -85.49707566753062, + 36.19301889766961 + ], + [ + -85.49702866680387, + 36.193041898142994 + ], + [ + -85.49696666701952, + 36.193074897614395 + ], + [ + -85.49693766688848, + 36.193079897664816 + ], + [ + -85.49688866719966, + 36.19312389779034 + ], + [ + -85.49683066700456, + 36.193174898214 + ], + [ + -85.496781667479, + 36.19324689819446 + ], + [ + -85.4967096674181, + 36.19344689811482 + ], + [ + -85.49667066744232, + 36.193575898445296 + ], + [ + -85.4966216674559, + 36.19379789804619 + ], + [ + -85.49660166657583, + 36.19389189837506 + ], + [ + -85.4966016674448, + 36.19404489830835 + ], + [ + -85.4966316669068, + 36.194334898138976 + ], + [ + -85.49663166700985, + 36.19446489827013 + ], + [ + -85.49660866747327, + 36.194528898046435 + ], + [ + -85.49656366717332, + 36.19458289779506 + ], + [ + -85.49651766648367, + 36.19462089805385 + ], + [ + -85.49641066690872, + 36.19466289867266 + ], + [ + -85.4961586669838, + 36.1947238980953 + ], + [ + -85.49607466641619, + 36.19476589795406 + ], + [ + -85.49605566638634, + 36.194782898152496 + ], + [ + -85.49600666721854, + 36.194822898181634 + ], + [ + -85.49595266720141, + 36.19489589803801 + ], + [ + -85.49592266636131, + 36.19499089832328 + ], + [ + -85.49592266673801, + 36.1953078986935 + ], + [ + -85.4958996667731, + 36.19559789814272 + ], + [ + -85.49585366667294, + 36.19578489828049 + ], + [ + -85.49584666673805, + 36.195814898536355 + ], + [ + -85.49578466684139, + 36.19602889892006 + ], + [ + -85.49570866708243, + 36.19647089900222 + ], + [ + -85.49570166661692, + 36.196573898270685 + ], + [ + -85.49569166745697, + 36.19663489871993 + ], + [ + -85.49566366671861, + 36.19682089872721 + ], + [ + -85.49565566703546, + 36.19688289889037 + ], + [ + -85.495641666861, + 36.197006898550434 + ], + [ + -85.49564166668755, + 36.19700989879336 + ], + [ + -85.49561766701935, + 36.19722989871678 + ], + [ + -85.4956036667589, + 36.19737889888647 + ], + [ + -85.49560166704082, + 36.19741289899846 + ], + [ + -85.49559966735957, + 36.19750389922605 + ], + [ + -85.49686266753763, + 36.19750789882652 + ], + [ + -85.49909066745455, + 36.19751689905266 + ], + [ + -85.49906266794781, + 36.19820889888868 + ], + [ + -85.49901566854479, + 36.198876898936895 + ], + [ + -85.49904466808084, + 36.198870898905 + ], + [ + -85.49907166779727, + 36.19885089875535 + ], + [ + -85.4990886682889, + 36.198822899133525 + ], + [ + -85.4990986680752, + 36.19878889882454 + ], + [ + -85.49910666786464, + 36.198752899369644 + ], + [ + -85.49911466761043, + 36.19871989925417 + ], + [ + -85.49911566764342, + 36.198717899035564 + ], + [ + -85.49913066851163, + 36.198685898600644 + ], + [ + -85.49915666854838, + 36.19866289854901 + ], + [ + -85.49917566844202, + 36.19864789864128 + ], + [ + -85.49920766757118, + 36.19863789867284 + ], + [ + -85.49927566834961, + 36.19868789871759 + ], + [ + -85.49927166805941, + 36.19877589852953 + ], + [ + -85.49924366820781, + 36.198812899249816 + ], + [ + -85.49920266755133, + 36.19884689897517 + ], + [ + -85.4991456684723, + 36.19889089922302 + ], + [ + -85.49909266788005, + 36.198934898907325 + ], + [ + -85.49897466752581, + 36.19902289888669 + ], + [ + -85.49892766819633, + 36.19904889941947 + ] + ] + ] + ] + } + } + ] +} \ No newline at end of file diff --git a/static/data/putnam_county_boundary.geojson b/static/data/putnam_county_boundary.geojson index 18d6117..6c2853f 100644 --- a/static/data/putnam_county_boundary.geojson +++ b/static/data/putnam_county_boundary.geojson @@ -1,8 +1,16497 @@ -{ -"type": "FeatureCollection", -"name": "putnam_county_boundary_2022", -"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, -"features": [ -{ "type": "Feature", "properties": { "NAME": "Putnam", "STATEFP": "47", "COUNTYFP": "141" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -85.326902, 36.200977 ], [ -85.299504, 36.19232 ], [ -85.274077, 36.181628 ], [ -85.273111, 36.182354 ], [ -85.272826, 36.18479 ], [ -85.271428, 36.18576 ], [ -85.266036, 36.184217 ], [ -85.264252, 36.185054 ], [ -85.265472, 36.18628 ], [ -85.264839, 36.186836 ], [ -85.260308, 36.185817 ], [ -85.262217, 36.18246 ], [ -85.261652, 36.180805 ], [ -85.263312, 36.181704 ], [ -85.265953, 36.181634 ], [ -85.263077, 36.176957 ], [ -85.249261, 36.170627 ], [ -85.204156, 36.152489 ], [ -85.119583, 36.144719 ], [ -85.100213, 36.138561 ], [ -85.107319, 36.132899 ], [ -85.1115, 36.132968 ], [ -85.114657, 36.13126 ], [ -85.117768, 36.131336 ], [ -85.119615, 36.130079 ], [ -85.121735, 36.130227 ], [ -85.127064, 36.128753 ], [ -85.129376, 36.130171 ], [ -85.131575, 36.130259 ], [ -85.131748, 36.128246 ], [ -85.15275, 36.099544 ], [ -85.235757, 36.105752 ], [ -85.236148, 36.100554 ], [ -85.238432, 36.103579 ], [ -85.239108, 36.100247 ], [ -85.246362, 36.100698 ], [ -85.248219, 36.084534 ], [ -85.244361, 36.079789 ], [ -85.24453, 36.077608 ], [ -85.242694, 36.074834 ], [ -85.2432, 36.073372 ], [ -85.24253, 36.072433 ], [ -85.245905, 36.069514 ], [ -85.24745, 36.063117 ], [ -85.245376, 36.057772 ], [ -85.243249, 36.05524 ], [ -85.24406, 36.053806 ], [ -85.245319, 36.054201 ], [ -85.245961, 36.053519 ], [ -85.244351, 36.05313 ], [ -85.245872, 36.051717 ], [ -85.244105, 36.050751 ], [ -85.244531, 36.04958 ], [ -85.247847, 36.050233 ], [ -85.247927, 36.04915 ], [ -85.246681, 36.048151 ], [ -85.247695, 36.046672 ], [ -85.246274, 36.04603 ], [ -85.248472, 36.04471 ], [ -85.247977, 36.043623 ], [ -85.248854, 36.040485 ], [ -85.251364, 36.040521 ], [ -85.260407, 35.996645 ], [ -85.254881, 35.99649 ], [ -85.257012, 35.978492 ], [ -85.264472, 35.979224 ], [ -85.311302, 35.992997 ], [ -85.311678, 36.022564 ], [ -85.310638, 36.022563 ], [ -85.309363, 36.031592 ], [ -85.32537, 36.033052 ], [ -85.326278, 36.030227 ], [ -85.356619, 36.045893 ], [ -85.372621, 36.049747 ], [ -85.372181, 36.052388 ], [ -85.390603, 36.053929 ], [ -85.507677, 36.082012 ], [ -85.5454, 36.077798 ], [ -85.552158, 36.072943 ], [ -85.556701, 36.073835 ], [ -85.560459, 36.073471 ], [ -85.570285, 36.058826 ], [ -85.573875, 36.059632 ], [ -85.575213, 36.05567 ], [ -85.578403, 36.056968 ], [ -85.57776, 36.063229 ], [ -85.579087, 36.063246 ], [ -85.582127, 36.060658 ], [ -85.580121, 36.06185 ], [ -85.580527, 36.061108 ], [ -85.582834, 36.060418 ], [ -85.580915, 36.058915 ], [ -85.580798, 36.057588 ], [ -85.58119, 36.056428 ], [ -85.584258, 36.055408 ], [ -85.583361, 36.05163 ], [ -85.582093, 36.051235 ], [ -85.582151, 36.050032 ], [ -85.583042, 36.050473 ], [ -85.58259, 36.046826 ], [ -85.584811, 36.044198 ], [ -85.589298, 36.043403 ], [ -85.587989, 36.042394 ], [ -85.58665, 36.039419 ], [ -85.58371, 36.037028 ], [ -85.586435, 36.037505 ], [ -85.588032, 36.038795 ], [ -85.588672, 36.040494 ], [ -85.593805, 36.04243 ], [ -85.594086, 36.043726 ], [ -85.597435, 36.041935 ], [ -85.602159, 36.042006 ], [ -85.601995, 36.043616 ], [ -85.606177, 36.044024 ], [ -85.609618, 36.046125 ], [ -85.611138, 36.04605 ], [ -85.612424, 36.043416 ], [ -85.618512, 36.040909 ], [ -85.619979, 36.036721 ], [ -85.623432, 36.036562 ], [ -85.623923, 36.034448 ], [ -85.622036, 36.033726 ], [ -85.622385, 36.033167 ], [ -85.62498, 36.031513 ], [ -85.638191, 36.027674 ], [ -85.638779, 36.026748 ], [ -85.638464, 36.023633 ], [ -85.643274, 36.024191 ], [ -85.643509, 36.019334 ], [ -85.644986, 36.013302 ], [ -85.647528, 36.013853 ], [ -85.651048, 36.012328 ], [ -85.653444, 36.012867 ], [ -85.652481, 36.017209 ], [ -85.655388, 36.017468 ], [ -85.668236, 36.029841 ], [ -85.686743, 36.064802 ], [ -85.688333, 36.069524 ], [ -85.68996, 36.068695 ], [ -85.699752, 36.070823 ], [ -85.722648, 36.081986 ], [ -85.725155, 36.081956 ], [ -85.729787, 36.079725 ], [ -85.730972, 36.081687 ], [ -85.741878, 36.078795 ], [ -85.741584, 36.077892 ], [ -85.740534, 36.077713 ], [ -85.740104, 36.075615 ], [ -85.74173, 36.07506 ], [ -85.747144, 36.07743 ], [ -85.753054, 36.067985 ], [ -85.756943, 36.067099 ], [ -85.767136, 36.070087 ], [ -85.768557, 36.077306 ], [ -85.775976, 36.078328 ], [ -85.774285, 36.083838 ], [ -85.772466, 36.085022 ], [ -85.770391, 36.089503 ], [ -85.769835, 36.094981 ], [ -85.773514, 36.095017 ], [ -85.772056, 36.097222 ], [ -85.773222, 36.098836 ], [ -85.77628, 36.100458 ], [ -85.776782, 36.102337 ], [ -85.780269, 36.10055 ], [ -85.788197, 36.103162 ], [ -85.788566, 36.104493 ], [ -85.787872, 36.105269 ], [ -85.788838, 36.107018 ], [ -85.791875, 36.107812 ], [ -85.793515, 36.10965 ], [ -85.794176, 36.113045 ], [ -85.796786, 36.113961 ], [ -85.797088, 36.116099 ], [ -85.797972, 36.116808 ], [ -85.797498, 36.121787 ], [ -85.802592, 36.122646 ], [ -85.800381, 36.125099 ], [ -85.805505, 36.127076 ], [ -85.806021, 36.12977 ], [ -85.807415, 36.131582 ], [ -85.804571, 36.134127 ], [ -85.802761, 36.138284 ], [ -85.801617, 36.138689 ], [ -85.802248, 36.140604 ], [ -85.805121, 36.142397 ], [ -85.803975, 36.144904 ], [ -85.807249, 36.147807 ], [ -85.805284, 36.165154 ], [ -85.80451, 36.165194 ], [ -85.803631, 36.170344 ], [ -85.799212, 36.170252 ], [ -85.785735, 36.172354 ], [ -85.783956, 36.173086 ], [ -85.783732, 36.174819 ], [ -85.784165, 36.176012 ], [ -85.787172, 36.17641 ], [ -85.786863, 36.180198 ], [ -85.790664, 36.180483 ], [ -85.791253, 36.179541 ], [ -85.793617, 36.179059 ], [ -85.792915, 36.178277 ], [ -85.793009, 36.173504 ], [ -85.795786, 36.174192 ], [ -85.798782, 36.171695 ], [ -85.800399, 36.172019 ], [ -85.799806, 36.174404 ], [ -85.803052, 36.174731 ], [ -85.803603, 36.179808 ], [ -85.805241, 36.17993 ], [ -85.80479, 36.183044 ], [ -85.800487, 36.182812 ], [ -85.798484, 36.186245 ], [ -85.795463, 36.187698 ], [ -85.795517, 36.191738 ], [ -85.799329, 36.191412 ], [ -85.800623, 36.193928 ], [ -85.798803, 36.201351 ], [ -85.799454, 36.206309 ], [ -85.801083, 36.206278 ], [ -85.803989, 36.204232 ], [ -85.806661, 36.203944 ], [ -85.809349, 36.206211 ], [ -85.80393, 36.211197 ], [ -85.786212, 36.210696 ], [ -85.782939, 36.211426 ], [ -85.781477, 36.214312 ], [ -85.783127, 36.219808 ], [ -85.778634, 36.221152 ], [ -85.782102, 36.224727 ], [ -85.781145, 36.225558 ], [ -85.782457, 36.230361 ], [ -85.786487, 36.23011 ], [ -85.780165, 36.236075 ], [ -85.779723, 36.237316 ], [ -85.782886, 36.24466 ], [ -85.780543, 36.24677 ], [ -85.779621, 36.24889 ], [ -85.777227, 36.248901 ], [ -85.775425, 36.245268 ], [ -85.769386, 36.245771 ], [ -85.767033, 36.243174 ], [ -85.766991, 36.241951 ], [ -85.762315, 36.237932 ], [ -85.759001, 36.236552 ], [ -85.756698, 36.236744 ], [ -85.753153, 36.233117 ], [ -85.747668, 36.232878 ], [ -85.742848, 36.228283 ], [ -85.739626, 36.22751 ], [ -85.734923, 36.229305 ], [ -85.732302, 36.228871 ], [ -85.728691, 36.231533 ], [ -85.72429, 36.233418 ], [ -85.721078, 36.232309 ], [ -85.719551, 36.232237 ], [ -85.718689, 36.233118 ], [ -85.706465, 36.23095 ], [ -85.707239, 36.228013 ], [ -85.704949, 36.227578 ], [ -85.702322, 36.225 ], [ -85.700279, 36.226138 ], [ -85.695292, 36.226729 ], [ -85.690311, 36.228916 ], [ -85.690256, 36.226122 ], [ -85.687827, 36.226198 ], [ -85.685964, 36.224617 ], [ -85.683277, 36.225552 ], [ -85.680809, 36.224404 ], [ -85.679282, 36.224627 ], [ -85.675, 36.228138 ], [ -85.675634, 36.222528 ], [ -85.674147, 36.221177 ], [ -85.673295, 36.221584 ], [ -85.671798, 36.220421 ], [ -85.671143, 36.220755 ], [ -85.672155, 36.222449 ], [ -85.671768, 36.223622 ], [ -85.665968, 36.223788 ], [ -85.665968, 36.222849 ], [ -85.661804, 36.222422 ], [ -85.660813, 36.221626 ], [ -85.660981, 36.220627 ], [ -85.659435, 36.219911 ], [ -85.659653, 36.218582 ], [ -85.656451, 36.218006 ], [ -85.654143, 36.216267 ], [ -85.65182, 36.217529 ], [ -85.650211, 36.220599 ], [ -85.644951, 36.221101 ], [ -85.643266, 36.220126 ], [ -85.639588, 36.223046 ], [ -85.638133, 36.221814 ], [ -85.635831, 36.222407 ], [ -85.635425, 36.221585 ], [ -85.636951, 36.217493 ], [ -85.634158, 36.213346 ], [ -85.629206, 36.212805 ], [ -85.6287, 36.215678 ], [ -85.625646, 36.215299 ], [ -85.625298, 36.21596 ], [ -85.619928, 36.216888 ], [ -85.616002, 36.21451 ], [ -85.615191, 36.215674 ], [ -85.613029, 36.215124 ], [ -85.613046, 36.221248 ], [ -85.604328, 36.220707 ], [ -85.604097, 36.223638 ], [ -85.59905, 36.223535 ], [ -85.600212, 36.226177 ], [ -85.594696, 36.225894 ], [ -85.594818, 36.225195 ], [ -85.593577, 36.225147 ], [ -85.593502, 36.225718 ], [ -85.589949, 36.226373 ], [ -85.58948, 36.229736 ], [ -85.585495, 36.229529 ], [ -85.583637, 36.230662 ], [ -85.575113, 36.230471 ], [ -85.57495, 36.22709 ], [ -85.572272, 36.226388 ], [ -85.572802, 36.222295 ], [ -85.568196, 36.220985 ], [ -85.563666, 36.222654 ], [ -85.557527, 36.223177 ], [ -85.557571, 36.225104 ], [ -85.559579, 36.226122 ], [ -85.558385, 36.227855 ], [ -85.557964, 36.230686 ], [ -85.556524, 36.231183 ], [ -85.554564, 36.229771 ], [ -85.551099, 36.229588 ], [ -85.551103, 36.230974 ], [ -85.555945, 36.231376 ], [ -85.555607, 36.234015 ], [ -85.544748, 36.234278 ], [ -85.544519, 36.236315 ], [ -85.53986, 36.236014 ], [ -85.538656, 36.24085 ], [ -85.526197, 36.239626 ], [ -85.525933, 36.242385 ], [ -85.533697, 36.242577 ], [ -85.532957, 36.247938 ], [ -85.530151, 36.247685 ], [ -85.529874, 36.250424 ], [ -85.53484, 36.250927 ], [ -85.533915, 36.258743 ], [ -85.530781, 36.258747 ], [ -85.530562, 36.261212 ], [ -85.519857, 36.260524 ], [ -85.520217, 36.257514 ], [ -85.512571, 36.256879 ], [ -85.51151, 36.264542 ], [ -85.509413, 36.264556 ], [ -85.509775, 36.266622 ], [ -85.508283, 36.27772 ], [ -85.50549, 36.278105 ], [ -85.505508, 36.278758 ], [ -85.494, 36.279817 ], [ -85.493896, 36.281337 ], [ -85.497279, 36.282493 ], [ -85.497149, 36.28386 ], [ -85.494656, 36.283423 ], [ -85.494038, 36.287095 ], [ -85.490441, 36.286801 ], [ -85.490332, 36.287518 ], [ -85.493165, 36.290028 ], [ -85.494727, 36.290587 ], [ -85.494627, 36.296686 ], [ -85.496827, 36.296389 ], [ -85.496622, 36.300937 ], [ -85.497733, 36.302345 ], [ -85.492974, 36.302277 ], [ -85.492898, 36.303348 ], [ -85.490759, 36.303309 ], [ -85.489993, 36.299446 ], [ -85.484231, 36.300524 ], [ -85.484212, 36.302835 ], [ -85.483473, 36.303693 ], [ -85.471211, 36.301981 ], [ -85.468895, 36.304343 ], [ -85.466816, 36.305146 ], [ -85.467434, 36.30234 ], [ -85.466143, 36.301123 ], [ -85.464755, 36.301905 ], [ -85.464485, 36.303538 ], [ -85.458729, 36.303986 ], [ -85.459251, 36.302155 ], [ -85.462189, 36.303171 ], [ -85.463206, 36.302829 ], [ -85.462747, 36.299631 ], [ -85.459248, 36.298101 ], [ -85.462957, 36.295165 ], [ -85.463475, 36.293476 ], [ -85.456049, 36.288857 ], [ -85.457058, 36.287629 ], [ -85.453405, 36.284957 ], [ -85.453907, 36.28096 ], [ -85.465812, 36.281478 ], [ -85.466011, 36.279383 ], [ -85.463454, 36.279087 ], [ -85.46348, 36.276956 ], [ -85.457979, 36.276308 ], [ -85.457985, 36.277108 ], [ -85.45365, 36.27684 ], [ -85.45423, 36.271739 ], [ -85.456293, 36.271809 ], [ -85.456823, 36.267783 ], [ -85.452647, 36.26758 ], [ -85.452888, 36.26146 ], [ -85.449842, 36.261257 ], [ -85.449179, 36.259412 ], [ -85.443056, 36.259038 ], [ -85.442904, 36.259679 ], [ -85.440442, 36.259534 ], [ -85.440095, 36.260977 ], [ -85.435652, 36.262233 ], [ -85.43293, 36.25583 ], [ -85.436509, 36.253577 ], [ -85.43585, 36.247148 ], [ -85.432677, 36.245701 ], [ -85.422656, 36.245278 ], [ -85.423178, 36.241659 ], [ -85.419472, 36.241495 ], [ -85.419432, 36.236145 ], [ -85.407132, 36.234424 ], [ -85.406166, 36.232862 ], [ -85.402361, 36.23225 ], [ -85.396138, 36.229275 ], [ -85.395737, 36.231171 ], [ -85.394952, 36.231107 ], [ -85.394806, 36.229181 ], [ -85.38583, 36.225977 ], [ -85.378569, 36.227781 ], [ -85.377304, 36.224949 ], [ -85.374249, 36.223015 ], [ -85.369521, 36.226204 ], [ -85.365182, 36.226194 ], [ -85.365625, 36.220588 ], [ -85.326706, 36.217867 ], [ -85.325653, 36.210091 ], [ -85.323679, 36.204163 ], [ -85.326902, 36.200977 ] ] ] } } -] -} +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "NAME": "Putnam County", + "source": "TNMap ArcGIS - all precincts dissolved" + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [ + -85.470617651234, + 36.07344187444798 + ], + [ + -85.4739496522577, + 36.07416287468049 + ], + [ + -85.47536865298821, + 36.07446887533033 + ], + [ + -85.47695365328988, + 36.0748118747506 + ], + [ + -85.48008465439977, + 36.075616874845785 + ], + [ + -85.48947865745974, + 36.07803387461364 + ], + [ + -85.49261065893845, + 36.078839874993584 + ], + [ + -85.49298165828083, + 36.078929874965624 + ], + [ + -85.49409565891514, + 36.079201874833245 + ], + [ + -85.49446765892041, + 36.079292874821725 + ], + [ + -85.49542165952968, + 36.079525874675994 + ], + [ + -85.49828465982262, + 36.0802258747518 + ], + [ + -85.4990186608927, + 36.08040587552686 + ], + [ + -85.4992436606278, + 36.08037687542327 + ], + [ + -85.49966966045567, + 36.0803208752277 + ], + [ + -85.4999806603453, + 36.080280874589974 + ], + [ + -85.50085466069136, + 36.08041187476481 + ], + [ + -85.50094666041518, + 36.08042487522592 + ], + [ + -85.50095566113599, + 36.08042687545257 + ], + [ + -85.50135766095605, + 36.0805518751014 + ], + [ + -85.50151966116191, + 36.08061087531888 + ], + [ + -85.50164566159631, + 36.08064687477235 + ], + [ + -85.50195066159482, + 36.080682874631194 + ], + [ + -85.50212366107388, + 36.0807138745999 + ], + [ + -85.50221066085437, + 36.080721874820895 + ], + [ + -85.50225566138296, + 36.08072887512758 + ], + [ + -85.50234066132963, + 36.08074187536486 + ], + [ + -85.50251266089501, + 36.08077887491791 + ], + [ + -85.50271466168834, + 36.080848875459786 + ], + [ + -85.50279866186447, + 36.08087387528547 + ], + [ + -85.50296966185383, + 36.080908874645836 + ], + [ + -85.50313966113364, + 36.080950875222506 + ], + [ + -85.50335566155886, + 36.08098587471678 + ], + [ + -85.50368966167491, + 36.08108487494045 + ], + [ + -85.50409866202683, + 36.08122387473914 + ], + [ + -85.50439666210669, + 36.081297875056045 + ], + [ + -85.50451766198415, + 36.081341874955726 + ], + [ + -85.50468666253553, + 36.081388874896305 + ], + [ + -85.50491666222624, + 36.08144687546343 + ], + [ + -85.5049396624137, + 36.08145287503825 + ], + [ + -85.50502266242569, + 36.08147787550733 + ], + [ + -85.50506566176252, + 36.0814848746567 + ], + [ + -85.50519266226316, + 36.081515875136624 + ], + [ + -85.5053616624681, + 36.081562875025746 + ], + [ + -85.50543666270075, + 36.081599875260686 + ], + [ + -85.50567866198743, + 36.08168687512587 + ], + [ + -85.50571566225669, + 36.08170587510842 + ], + [ + -85.50578966266883, + 36.08173087510182 + ], + [ + -85.5058386629168, + 36.081746875234096 + ], + [ + -85.50588966196597, + 36.081755875252796 + ], + [ + -85.50596666252767, + 36.08177087521003 + ], + [ + -85.50600966210494, + 36.081772874670634 + ], + [ + -85.50617966258582, + 36.08180987519642 + ], + [ + -85.50619366218145, + 36.08181187477557 + ], + [ + -85.50626466298783, + 36.081824875282905 + ], + [ + -85.50629566223927, + 36.081833875240946 + ], + [ + -85.50630566297477, + 36.08183687506597 + ], + [ + -85.50647866237144, + 36.08186087489485 + ], + [ + -85.5065316622992, + 36.08186487536871 + ], + [ + -85.50661566261357, + 36.08187287544678 + ], + [ + -85.50674066273307, + 36.081884875417124 + ], + [ + -85.50687066219024, + 36.08190087481786 + ], + [ + -85.50708566307928, + 36.081935875232546 + ], + [ + -85.50724466312606, + 36.08194787488227 + ], + [ + -85.5072606624261, + 36.08194987529993 + ], + [ + -85.50743366346201, + 36.0819738746636 + ], + [ + -85.50748266307824, + 36.08197787549979 + ], + [ + -85.5078776631735, + 36.08205887511006 + ], + [ + -85.50794666339613, + 36.08204987544366 + ], + [ + -85.50817066292835, + 36.082020875333434 + ], + [ + -85.50843366273722, + 36.08198687494865 + ], + [ + -85.50845966292249, + 36.08198487468223 + ], + [ + -85.50932766310738, + 36.08194387494018 + ], + [ + -85.50936366341948, + 36.08193887518934 + ], + [ + -85.5098366633022, + 36.081884875243524 + ], + [ + -85.51087566396968, + 36.08176487447496 + ], + [ + -85.51399866457851, + 36.08140687504886 + ], + [ + -85.51504066471979, + 36.08128887476995 + ], + [ + -85.51505666462299, + 36.08128587480564 + ], + [ + -85.51510666509041, + 36.08128087467586 + ], + [ + -85.51512366481391, + 36.0812788746282 + ], + [ + -85.51522566479493, + 36.08126587478921 + ], + [ + -85.5155346656957, + 36.0812308749376 + ], + [ + -85.51563766483547, + 36.08121987417912 + ], + [ + -85.51608466572893, + 36.08116687458646 + ], + [ + -85.51742566549568, + 36.08101287422577 + ], + [ + -85.5177856653948, + 36.08097187469025 + ], + [ + -85.51787566636928, + 36.08096387443999 + ], + [ + -85.51795966553381, + 36.08095287412989 + ], + [ + -85.51825066565823, + 36.08091887417348 + ], + [ + -85.51938166620424, + 36.080788874340094 + ], + [ + -85.51970466679717, + 36.08075287414542 + ], + [ + -85.51975966674021, + 36.08074687446951 + ], + [ + -85.51981466668039, + 36.08074087386727 + ], + [ + -85.52023466675703, + 36.080691874675416 + ], + [ + -85.52166066666771, + 36.08052887427182 + ], + [ + -85.52213666725488, + 36.0804748740759 + ], + [ + -85.5235476671044, + 36.08031187393641 + ], + [ + -85.52778166896358, + 36.07982687383581 + ], + [ + -85.52919366935848, + 36.07966587371064 + ], + [ + -85.53036466982302, + 36.07952987331027 + ], + [ + -85.53388167019502, + 36.07912587330983 + ], + [ + -85.53505467084715, + 36.07899287331684 + ], + [ + -85.53670967101625, + 36.07880187344865 + ], + [ + -85.54167667253635, + 36.07823387259527 + ], + [ + -85.54333367292003, + 36.07804487232261 + ], + [ + -85.54374767284344, + 36.077995873118354 + ], + [ + -85.54498967325628, + 36.07785287223186 + ], + [ + -85.54540467350903, + 36.077804872429724 + ], + [ + -85.54596367370814, + 36.07735287208416 + ], + [ + -85.54764267412551, + 36.07599887178011 + ], + [ + -85.5482026745214, + 36.07554787232549 + ], + [ + -85.54847267487146, + 36.0754988723682 + ], + [ + -85.5485796740938, + 36.07547987231962 + ], + [ + -85.54900767413616, + 36.07541887193821 + ], + [ + -85.54928467403079, + 36.07536487176485 + ], + [ + -85.54940467490567, + 36.075342872396426 + ], + [ + -85.5495496743101, + 36.07529287190644 + ], + [ + -85.54967967431564, + 36.07521687213691 + ], + [ + -85.54976267519433, + 36.07514487229595 + ], + [ + -85.54982567478818, + 36.075069872185736 + ], + [ + -85.54994667468593, + 36.07492687139624 + ], + [ + -85.55015267435422, + 36.074655871958164 + ], + [ + -85.55045367435544, + 36.07419887156762 + ], + [ + -85.55048067512017, + 36.07415987151314 + ], + [ + -85.55064067538575, + 36.073892871657634 + ], + [ + -85.55066967433413, + 36.07384287178436 + ], + [ + -85.55075967541462, + 36.073700871280195 + ], + [ + -85.5507916747447, + 36.07365387117132 + ], + [ + -85.55108467480969, + 36.07353487184216 + ], + [ + -85.55177367506852, + 36.07325887162528 + ], + [ + -85.5519566750023, + 36.073146871384864 + ], + [ + -85.55204767566119, + 36.07309187151817 + ], + [ + -85.5521626757135, + 36.07294987091331 + ], + [ + -85.55296467592545, + 36.07320387140213 + ], + [ + -85.55326467519285, + 36.07326987140028 + ], + [ + -85.55492867565837, + 36.07364187103414 + ], + [ + -85.55666367683695, + 36.07383587163668 + ], + [ + -85.55670567622154, + 36.073841871724426 + ], + [ + -85.55763867705902, + 36.07382687152751 + ], + [ + -85.55781167671947, + 36.07382287097704 + ], + [ + -85.55833867731374, + 36.07373487136489 + ], + [ + -85.55910967781809, + 36.0736078709204 + ], + [ + -85.55993067797016, + 36.073528871304646 + ], + [ + -85.56046367767539, + 36.07347787065689 + ], + [ + -85.56097967826406, + 36.0727068706998 + ], + [ + -85.56252967824905, + 36.070397870343 + ], + [ + -85.56304667786769, + 36.06962787056 + ], + [ + -85.56313767781123, + 36.069486870209694 + ], + [ + -85.56341767873761, + 36.069070869922605 + ], + [ + -85.56351167826323, + 36.06893387006851 + ], + [ + -85.56386767864143, + 36.06839986999067 + ], + [ + -85.56494267901141, + 36.06679786948009 + ], + [ + -85.56530267883656, + 36.066265869405335 + ], + [ + -85.56534367810276, + 36.06620386913714 + ], + [ + -85.56546967827678, + 36.066017869153875 + ], + [ + -85.5655116786554, + 36.065956869241575 + ], + [ + -85.5664196789411, + 36.06460086884325 + ], + [ + -85.56914667971648, + 36.06053586817046 + ], + [ + -85.57001667936497, + 36.05923986803743 + ], + [ + -85.57005667941434, + 36.05918086736994 + ], + [ + -85.57009267943909, + 36.05912486808194 + ], + [ + -85.57015267910204, + 36.05903486821678 + ], + [ + -85.57028967976848, + 36.05883286763654 + ], + [ + -85.5705696791156, + 36.05883686769673 + ], + [ + -85.57068767916962, + 36.05883986749888 + ], + [ + -85.57074667956965, + 36.05884386749029 + ], + [ + -85.57074167993355, + 36.058886867462114 + ], + [ + -85.57074067962164, + 36.058892867281415 + ], + [ + -85.57073567931168, + 36.05902886784318 + ], + [ + -85.57073067929137, + 36.059046867390734 + ], + [ + -85.57072067923234, + 36.05909786817936 + ], + [ + -85.5708356796818, + 36.059206867620546 + ], + [ + -85.57096368019232, + 36.05930586763147 + ], + [ + -85.57109967947467, + 36.05939686742428 + ], + [ + -85.57117867990257, + 36.059428867924964 + ], + [ + -85.5714706800747, + 36.05951586807388 + ], + [ + -85.57154967966521, + 36.059547867425294 + ], + [ + -85.57167167950243, + 36.059588868240624 + ], + [ + -85.57179668007792, + 36.05962486761206 + ], + [ + -85.57201168039863, + 36.05967086793758 + ], + [ + -85.57209868058253, + 36.059685867581976 + ], + [ + -85.57236268004408, + 36.05971486746298 + ], + [ + -85.5724946798022, + 36.05972586799359 + ], + [ + -85.57253967994605, + 36.059725868067694 + ], + [ + -85.57284868061716, + 36.059752867920245 + ], + [ + -85.57293668014569, + 36.05975586824655 + ], + [ + -85.57311368075526, + 36.05976986744694 + ], + [ + -85.57337968062606, + 36.059765867562874 + ], + [ + -85.57360068069664, + 36.05973986735669 + ], + [ + -85.57368568011151, + 36.05971986775077 + ], + [ + -85.57372968014447, + 36.05971386767359 + ], + [ + -85.57377168088432, + 36.05970086753278 + ], + [ + -85.57380968027151, + 36.05968286738631 + ], + [ + -85.57387968021477, + 36.0596388677804 + ], + [ + -85.57391068067128, + 36.059612867492774 + ], + [ + -85.57396168059049, + 36.05955586740192 + ], + [ + -85.57407368041727, + 36.05936186763673 + ], + [ + -85.57410068119903, + 36.05929386735647 + ], + [ + -85.57422468094556, + 36.05902786747282 + ], + [ + -85.57425168037467, + 36.05895986714944 + ], + [ + -85.57428468040983, + 36.05885786765338 + ], + [ + -85.57429868019777, + 36.058786867929015 + ], + [ + -85.57431868107986, + 36.05871886720321 + ], + [ + -85.5743336802837, + 36.05868486736839 + ], + [ + -85.57440768110588, + 36.05844386774606 + ], + [ + -85.57442568090596, + 36.05833786765622 + ], + [ + -85.57445768103223, + 36.057982867219486 + ], + [ + -85.57445968110466, + 36.05780586778686 + ], + [ + -85.5744646809764, + 36.05773486689455 + ], + [ + -85.57449268037655, + 36.05752186684299 + ], + [ + -85.57451268019402, + 36.05741686698244 + ], + [ + -85.57457468119897, + 36.05717286684959 + ], + [ + -85.57461268042127, + 36.05707086684494 + ], + [ + -85.57468668019715, + 36.05675786691708 + ], + [ + -85.57476968071205, + 36.05655486668796 + ], + [ + -85.57480268070219, + 36.05648886747498 + ], + [ + -85.57484468065536, + 36.05638686707647 + ], + [ + -85.57488668126199, + 36.05624986659894 + ], + [ + -85.57492368083507, + 36.05610986705153 + ], + [ + -85.57494968083873, + 36.05604186661105 + ], + [ + -85.57499668103844, + 36.05594186658664 + ], + [ + -85.57503268095138, + 36.05587686657856 + ], + [ + -85.57508068022784, + 36.055816866771316 + ], + [ + -85.57518368084548, + 36.05570086729697 + ], + [ + -85.575217681007, + 36.055676867241786 + ], + [ + -85.57532668065421, + 36.05561586649188 + ], + [ + -85.57544968031618, + 36.05557386694605 + ], + [ + -85.57562268133731, + 36.05553786647367 + ], + [ + -85.57571168147089, + 36.055538866586915 + ], + [ + -85.57582168078994, + 36.05554686683077 + ], + [ + -85.57586268091686, + 36.055558866476574 + ], + [ + -85.57595068047486, + 36.05557286647124 + ], + [ + -85.57624268073917, + 36.05566086639933 + ], + [ + -85.57653968125351, + 36.05573086672098 + ], + [ + -85.57662168169594, + 36.05575886716391 + ], + [ + -85.57694168101362, + 36.05588386729531 + ], + [ + -85.57705768150547, + 36.05593686643987 + ], + [ + -85.5771316813187, + 36.05597686685646 + ], + [ + -85.5772126817498, + 36.05600786642953 + ], + [ + -85.57736968168622, + 36.05607586658067 + ], + [ + -85.57744368155089, + 36.05611486729111 + ], + [ + -85.57769468192464, + 36.056263866837895 + ], + [ + -85.57776868125305, + 36.05630386685642 + ], + [ + -85.57780368209527, + 36.056326866495496 + ], + [ + -85.57802068202948, + 36.05650486729573 + ], + [ + -85.57819368183515, + 36.05666686662345 + ], + [ + -85.57829468224445, + 36.056783867375 + ], + [ + -85.57836068206944, + 36.05687486733533 + ], + [ + -85.57840768186892, + 36.05697486670812 + ], + [ + -85.57845068169574, + 36.0571118673695 + ], + [ + -85.57845768158381, + 36.05718186671967 + ], + [ + -85.57851468203417, + 36.05742486730992 + ], + [ + -85.57851968210186, + 36.0574958672813 + ], + [ + -85.5785206816397, + 36.05774186701875 + ], + [ + -85.57850568209385, + 36.05788386695003 + ], + [ + -85.57849168147665, + 36.05812986738361 + ], + [ + -85.57842468174012, + 36.058624867335254 + ], + [ + -85.57838768144941, + 36.05883686695496 + ], + [ + -85.57835768222908, + 36.05904686768694 + ], + [ + -85.5782706820164, + 36.059467867869145 + ], + [ + -85.57815368197622, + 36.05995686764026 + ], + [ + -85.57813168164205, + 36.06002486738165 + ], + [ + -85.57809068168184, + 36.06019986731512 + ], + [ + -85.57805768178501, + 36.060376868119825 + ], + [ + -85.57804968217052, + 36.06044686810638 + ], + [ + -85.577965681563, + 36.06090586798426 + ], + [ + -85.57791268214818, + 36.06111586818841 + ], + [ + -85.57785168187856, + 36.061322867941755 + ], + [ + -85.57775468154632, + 36.06159686832472 + ], + [ + -85.57774768227988, + 36.0616318681558 + ], + [ + -85.57770368240499, + 36.06176886785247 + ], + [ + -85.57763168193746, + 36.06208486846781 + ], + [ + -85.57758968166712, + 36.06225886784095 + ], + [ + -85.57756868168963, + 36.0623278677594 + ], + [ + -85.57754568181322, + 36.062469868229 + ], + [ + -85.57752868238039, + 36.06268186810633 + ], + [ + -85.57752868183489, + 36.06278886804911 + ], + [ + -85.57754268192738, + 36.062894867978585 + ], + [ + -85.57757768189694, + 36.06299686865065 + ], + [ + -85.5776276814358, + 36.06309686787135 + ], + [ + -85.5776746823462, + 36.06315686801746 + ], + [ + -85.5777646818708, + 36.063235867979586 + ], + [ + -85.57787168161188, + 36.06329986805329 + ], + [ + -85.57794868262387, + 36.06333586862408 + ], + [ + -85.57803068253061, + 36.063363868027125 + ], + [ + -85.57811568259876, + 36.06338586819232 + ], + [ + -85.5782446821805, + 36.06341086847501 + ], + [ + -85.57833268223831, + 36.063416867884726 + ], + [ + -85.5785116818541, + 36.063415868038504 + ], + [ + -85.57864368238674, + 36.063401868627764 + ], + [ + -85.57881668257684, + 36.0633668684981 + ], + [ + -85.57898068205621, + 36.06331286851038 + ], + [ + -85.5790916820439, + 36.0632528682327 + ], + [ + -85.57916068290513, + 36.0632078680044 + ], + [ + -85.57925668241487, + 36.06313386801519 + ], + [ + -85.57928368223193, + 36.06310486829194 + ], + [ + -85.57934768222358, + 36.06305586777942 + ], + [ + -85.5795786829271, + 36.06284086770118 + ], + [ + -85.57985268206997, + 36.062559868185645 + ], + [ + -85.5799416823161, + 36.06248086822464 + ], + [ + -85.58005268215669, + 36.06236886815449 + ], + [ + -85.5804136821911, + 36.06205286758939 + ], + [ + -85.58093868228603, + 36.061567868131405 + ], + [ + -85.58115968262544, + 36.06139086750993 + ], + [ + -85.58126068312788, + 36.06132086781008 + ], + [ + -85.58148868319904, + 36.061148867723546 + ], + [ + -85.58179868330511, + 36.06089286743735 + ], + [ + -85.58186368340107, + 36.06084386724224 + ], + [ + -85.58193468361604, + 36.060800867766204 + ], + [ + -85.58206468340958, + 36.060702867191644 + ], + [ + -85.5821316832846, + 36.06066486750716 + ], + [ + -85.58192768302916, + 36.060697867692326 + ], + [ + -85.58173168322918, + 36.060729867844856 + ], + [ + -85.58158668321866, + 36.06075286797103 + ], + [ + -85.58115468254385, + 36.0610348673468 + ], + [ + -85.5811016827188, + 36.06108186748309 + ], + [ + -85.58045568272502, + 36.06166086748931 + ], + [ + -85.58012568225301, + 36.061856868133084 + ], + [ + -85.58029068315459, + 36.06152886771957 + ], + [ + -85.58045368213546, + 36.06124486742369 + ], + [ + -85.58053168291386, + 36.06111486807273 + ], + [ + -85.58114668238525, + 36.06082286746306 + ], + [ + -85.58172268284235, + 36.060549867805996 + ], + [ + -85.5818316825853, + 36.06049886716586 + ], + [ + -85.58211168317759, + 36.06047786793364 + ], + [ + -85.58255468285321, + 36.06044586755232 + ], + [ + -85.58272268348499, + 36.06043786736019 + ], + [ + -85.58277068344485, + 36.06043486735316 + ], + [ + -85.58283868350208, + 36.06042486786799 + ], + [ + -85.58281968320277, + 36.06035686778667 + ], + [ + -85.58279968282673, + 36.060288867844726 + ], + [ + -85.58278668347356, + 36.06024386737277 + ], + [ + -85.58196468287144, + 36.059389867247404 + ], + [ + -85.58158568288921, + 36.05914886767539 + ], + [ + -85.58122868307274, + 36.05913486762513 + ], + [ + -85.58091968287502, + 36.05892186742511 + ], + [ + -85.5808026828342, + 36.05759486679063 + ], + [ + -85.58082568244913, + 36.05743786657105 + ], + [ + -85.58089868270356, + 36.056948866917594 + ], + [ + -85.58091068214648, + 36.05692786661261 + ], + [ + -85.58119468298848, + 36.05643486673284 + ], + [ + -85.58183768300924, + 36.0562698671211 + ], + [ + -85.58196668262286, + 36.05626186697587 + ], + [ + -85.58229668281987, + 36.05624286673305 + ], + [ + -85.5828766825727, + 36.05621586703299 + ], + [ + -85.58302368362695, + 36.05616586652578 + ], + [ + -85.58362568300895, + 36.055953866435374 + ], + [ + -85.58412568327715, + 36.05576786647243 + ], + [ + -85.5842626834457, + 36.05541486685143 + ], + [ + -85.5839336831162, + 36.054282866506725 + ], + [ + -85.58390368332891, + 36.0540248661349 + ], + [ + -85.58374168346818, + 36.053292866146904 + ], + [ + -85.58370968291668, + 36.05313986619486 + ], + [ + -85.58347668322519, + 36.05288486582657 + ], + [ + -85.58346668313251, + 36.05287786621923 + ], + [ + -85.58327568281013, + 36.052733865509 + ], + [ + -85.58332268276173, + 36.052604865508656 + ], + [ + -85.58345368245355, + 36.052126866104366 + ], + [ + -85.58340768259964, + 36.05178886533279 + ], + [ + -85.58336568249747, + 36.05163686532899 + ], + [ + -85.58319368330972, + 36.05146086548136 + ], + [ + -85.58296068241789, + 36.05120586579123 + ], + [ + -85.58271868220494, + 36.050999865609484 + ], + [ + -85.5823266827332, + 36.05125486534315 + ], + [ + -85.58210868305535, + 36.05129786561524 + ], + [ + -85.58209768235005, + 36.051241865533456 + ], + [ + -85.58214468270388, + 36.05108786549083 + ], + [ + -85.58228968220357, + 36.0508438658674 + ], + [ + -85.58249368209783, + 36.05059186526254 + ], + [ + -85.58208868228235, + 36.05017686568997 + ], + [ + -85.58215568294688, + 36.05003886589247 + ], + [ + -85.58218968201946, + 36.05002286536808 + ], + [ + -85.58226368302611, + 36.049989865756146 + ], + [ + -85.58233368289646, + 36.050020865272955 + ], + [ + -85.58280768280889, + 36.05044186566168 + ], + [ + -85.58304668286502, + 36.0504798658387 + ], + [ + -85.58300568299582, + 36.05041586507077 + ], + [ + -85.58289568302983, + 36.050343865526465 + ], + [ + -85.58282268211845, + 36.05015186575832 + ], + [ + -85.5827786829536, + 36.050088865755015 + ], + [ + -85.582606682075, + 36.049719865097785 + ], + [ + -85.58247168227814, + 36.049427865695066 + ], + [ + -85.58238368215615, + 36.04890286555215 + ], + [ + -85.58244968206263, + 36.048545864869396 + ], + [ + -85.5825246829581, + 36.048146865238664 + ], + [ + -85.58286268284714, + 36.047409865102345 + ], + [ + -85.58289768284872, + 36.04731286481841 + ], + [ + -85.58291968222503, + 36.04725586499465 + ], + [ + -85.582839682982, + 36.04719186522975 + ], + [ + -85.58264768256122, + 36.04700886524002 + ], + [ + -85.58259468219222, + 36.04683286447897 + ], + [ + -85.58270168267555, + 36.04667786506813 + ], + [ + -85.5830846824012, + 36.046470864465405 + ], + [ + -85.58310168297916, + 36.04633386451192 + ], + [ + -85.58305868286592, + 36.04614086441632 + ], + [ + -85.58302568294863, + 36.0459318643037 + ], + [ + -85.58307268230726, + 36.04580186469268 + ], + [ + -85.58324068239081, + 36.045791864827244 + ], + [ + -85.5833696826288, + 36.04576586474832 + ], + [ + -85.58344468238914, + 36.0455308640365 + ], + [ + -85.58360468255162, + 36.045309864500155 + ], + [ + -85.58384168206423, + 36.04498586429966 + ], + [ + -85.5842226823074, + 36.044643864549556 + ], + [ + -85.58480568311735, + 36.04421086390788 + ], + [ + -85.58481568244486, + 36.04420486431686 + ], + [ + -85.58599268325185, + 36.043843863603655 + ], + [ + -85.58675768355184, + 36.04375886371778 + ], + [ + -85.5869226829138, + 36.04374386438521 + ], + [ + -85.58722368320886, + 36.04371686430374 + ], + [ + -85.58729468328272, + 36.04367786412663 + ], + [ + -85.5875756839681, + 36.043480863605325 + ], + [ + -85.58768668415374, + 36.04341786391764 + ], + [ + -85.58798468388443, + 36.04341386395016 + ], + [ + -85.58819368351213, + 36.04344786426674 + ], + [ + -85.58841868388829, + 36.04348486424001 + ], + [ + -85.58885468352588, + 36.043523863676825 + ], + [ + -85.58930268389325, + 36.043409863991165 + ], + [ + -85.58875568340616, + 36.04284486327986 + ], + [ + -85.58831268346819, + 36.04238686333854 + ], + [ + -85.5881796842184, + 36.04239186341599 + ], + [ + -85.58799368372381, + 36.04240086406847 + ], + [ + -85.58799368361869, + 36.042190864019815 + ], + [ + -85.5879946841299, + 36.04205886382565 + ], + [ + -85.58786168314987, + 36.041782863638986 + ], + [ + -85.58771368397953, + 36.04158686333454 + ], + [ + -85.58764068378959, + 36.04148986334142 + ], + [ + -85.58759168322057, + 36.04102086339258 + ], + [ + -85.58751868318184, + 36.040696863309776 + ], + [ + -85.58710568360574, + 36.04009786286621 + ], + [ + -85.5870586827172, + 36.040028863200234 + ], + [ + -85.58679668365494, + 36.03964286306043 + ], + [ + -85.58672568333057, + 36.039534862925386 + ], + [ + -85.58665468284832, + 36.0394258632286 + ], + [ + -85.58651568250588, + 36.039407862818685 + ], + [ + -85.58645868298068, + 36.03933886284347 + ], + [ + -85.58644268264403, + 36.039317863159 + ], + [ + -85.58642268350498, + 36.03928886353368 + ], + [ + -85.58640068282409, + 36.039260862859805 + ], + [ + -85.58641668352571, + 36.03920586328232 + ], + [ + -85.58628468314615, + 36.039121862690145 + ], + [ + -85.58584368292523, + 36.03878386325328 + ], + [ + -85.58533968212143, + 36.03839786327769 + ], + [ + -85.58482868211559, + 36.03819686291732 + ], + [ + -85.58436568242433, + 36.03805286319179 + ], + [ + -85.58406268274469, + 36.03787886257875 + ], + [ + -85.58397768199883, + 36.03779786246886 + ], + [ + -85.58361668241753, + 36.037452862998826 + ], + [ + -85.58364668177323, + 36.037242862685794 + ], + [ + -85.58366268207288, + 36.03713886279303 + ], + [ + -85.5837146823606, + 36.037034862895716 + ], + [ + -85.58408068233713, + 36.03704086298937 + ], + [ + -85.58448768276655, + 36.037171862722424 + ], + [ + -85.58471068204267, + 36.03724386304302 + ], + [ + -85.58506268287084, + 36.03747486264387 + ], + [ + -85.58543668264102, + 36.0375848629085 + ], + [ + -85.5860816822791, + 36.03750586256365 + ], + [ + -85.58631868245725, + 36.03750986246035 + ], + [ + -85.58643968265969, + 36.037511863153654 + ], + [ + -85.58653368303332, + 36.03773086261897 + ], + [ + -85.58664768268439, + 36.03794986300885 + ], + [ + -85.58711868298147, + 36.03814286307547 + ], + [ + -85.58715868330684, + 36.03815986302136 + ], + [ + -85.58755968308554, + 36.03838286294492 + ], + [ + -85.5879466830885, + 36.038721862959 + ], + [ + -85.58803668324057, + 36.03880186248908 + ], + [ + -85.58830068393166, + 36.03938586292046 + ], + [ + -85.58843368349405, + 36.03960786278542 + ], + [ + -85.5885396832898, + 36.03978486328941 + ], + [ + -85.5885986832273, + 36.04026986322265 + ], + [ + -85.58867668400013, + 36.04050086349852 + ], + [ + -85.58924768383018, + 36.04096486318518 + ], + [ + -85.58968768382631, + 36.0412218632173 + ], + [ + -85.59019668446989, + 36.041495863635305 + ], + [ + -85.59048768393235, + 36.04155786299339 + ], + [ + -85.5906796839711, + 36.04159986316995 + ], + [ + -85.59124168494183, + 36.0417288633894 + ], + [ + -85.59145768412162, + 36.04185286341707 + ], + [ + -85.59174768440614, + 36.04199986379084 + ], + [ + -85.59181768467742, + 36.0420238630654 + ], + [ + -85.59208668527424, + 36.042119863075115 + ], + [ + -85.5924386853886, + 36.04233686373392 + ], + [ + -85.59266468525273, + 36.04229986351494 + ], + [ + -85.59293468500175, + 36.042329863657024 + ], + [ + -85.59333368515416, + 36.042374863217326 + ], + [ + -85.5934286854334, + 36.04238586299446 + ], + [ + -85.59371368527901, + 36.04242386305252 + ], + [ + -85.59380968531654, + 36.042436863126866 + ], + [ + -85.5938196854256, + 36.04248786308492 + ], + [ + -85.5938526854867, + 36.042640863146346 + ], + [ + -85.5938656853078, + 36.04269186329401 + ], + [ + -85.59392368512627, + 36.04296886308973 + ], + [ + -85.59403068553043, + 36.04345686335044 + ], + [ + -85.59409068583511, + 36.04373286392879 + ], + [ + -85.59415968536679, + 36.0437198637946 + ], + [ + -85.59443968575428, + 36.04367186357598 + ], + [ + -85.59538868572781, + 36.04342586356394 + ], + [ + -85.59597368651599, + 36.04327486363752 + ], + [ + -85.59626668625609, + 36.04303786379496 + ], + [ + -85.59659768609868, + 36.042405863041424 + ], + [ + -85.59743968585362, + 36.04194186296576 + ], + [ + -85.59772168658895, + 36.04189386271293 + ], + [ + -85.59827468704518, + 36.04180186307407 + ], + [ + -85.5986816866069, + 36.04190486329075 + ], + [ + -85.59870868638382, + 36.04190886294031 + ], + [ + -85.59878868634698, + 36.04192186344782 + ], + [ + -85.59881668651238, + 36.0419268632149 + ], + [ + -85.59885968694584, + 36.04193386327088 + ], + [ + -85.59898968706035, + 36.04195486306525 + ], + [ + -85.59903368629305, + 36.04196186277758 + ], + [ + -85.59922068679373, + 36.041992863260205 + ], + [ + -85.5999546866658, + 36.04199686320684 + ], + [ + -85.60216368790897, + 36.042012862802515 + ], + [ + -85.60210568814921, + 36.04257486270351 + ], + [ + -85.60201268767531, + 36.043494863293866 + ], + [ + -85.60199968728918, + 36.04362286364702 + ], + [ + -85.60214168788877, + 36.0436138637526 + ], + [ + -85.60267168774448, + 36.04358086283128 + ], + [ + -85.60320268815038, + 36.043547862874775 + ], + [ + -85.60348768811012, + 36.04353086288554 + ], + [ + -85.60354868801507, + 36.04354786335432 + ], + [ + -85.60397268839847, + 36.04366686312152 + ], + [ + -85.60412768820973, + 36.04370986302631 + ], + [ + -85.60618168905927, + 36.04403086316216 + ], + [ + -85.60628468955588, + 36.04407086303797 + ], + [ + -85.60678168893917, + 36.044265863355896 + ], + [ + -85.60698368974735, + 36.04438686326074 + ], + [ + -85.60772068997927, + 36.044827863741084 + ], + [ + -85.60821468969263, + 36.04522286373429 + ], + [ + -85.60826568984602, + 36.045263863585205 + ], + [ + -85.60885968989284, + 36.04574786321509 + ], + [ + -85.60962269048692, + 36.0461318639272 + ], + [ + -85.61050569050195, + 36.046224863814594 + ], + [ + -85.61114269012586, + 36.046056863052065 + ], + [ + -85.61129669041657, + 36.04581786329373 + ], + [ + -85.6116216910976, + 36.04500786312172 + ], + [ + -85.61201869025317, + 36.044037863317705 + ], + [ + -85.61242869066092, + 36.04342286289815 + ], + [ + -85.61282969094478, + 36.043175862371235 + ], + [ + -85.61298469064211, + 36.04308086296352 + ], + [ + -85.61378469158159, + 36.04258786257543 + ], + [ + -85.61617569163622, + 36.04173986196074 + ], + [ + -85.61623069208544, + 36.041720862503034 + ], + [ + -85.61669469223122, + 36.04158186205006 + ], + [ + -85.61680669188866, + 36.04155386233162 + ], + [ + -85.61684769237118, + 36.041535862650626 + ], + [ + -85.61784569182758, + 36.04123686210077 + ], + [ + -85.61851669190469, + 36.040915862076844 + ], + [ + -85.61885469253612, + 36.04038886179 + ], + [ + -85.61890069291209, + 36.04017586195142 + ], + [ + -85.61914169220073, + 36.039061861405116 + ], + [ + -85.61931269263798, + 36.038051861629285 + ], + [ + -85.61933469290439, + 36.037919861769765 + ], + [ + -85.61959269254247, + 36.03744586094453 + ], + [ + -85.61977069216282, + 36.03711886119786 + ], + [ + -85.61994869219345, + 36.036792861586115 + ], + [ + -85.61998369229418, + 36.036727861083044 + ], + [ + -85.62003369272041, + 36.03669886157825 + ], + [ + -85.62041869281248, + 36.036474861418085 + ], + [ + -85.62080369261591, + 36.03625186134014 + ], + [ + -85.62086669221787, + 36.03621586072493 + ], + [ + -85.62091369245074, + 36.03622086107071 + ], + [ + -85.62099769300343, + 36.03622986138233 + ], + [ + -85.62146969251974, + 36.036281860695375 + ], + [ + -85.62203669324059, + 36.03661386091545 + ], + [ + -85.62220469361556, + 36.03671186077419 + ], + [ + -85.62298669382231, + 36.03675586068946 + ], + [ + -85.6234366933582, + 36.036568860580786 + ], + [ + -85.62349069387896, + 36.036498861382 + ], + [ + -85.62373669369317, + 36.03618086107188 + ], + [ + -85.62383369410696, + 36.03605786124698 + ], + [ + -85.62409469398253, + 36.035161860879484 + ], + [ + -85.62411169312006, + 36.035102860485004 + ], + [ + -85.6240526938948, + 36.03489486102432 + ], + [ + -85.62392769382497, + 36.034454860770055 + ], + [ + -85.62363769364818, + 36.034095860120985 + ], + [ + -85.62278269322307, + 36.03378486063991 + ], + [ + -85.6220406924926, + 36.03373286064845 + ], + [ + -85.62198669284692, + 36.033528860318 + ], + [ + -85.62197669276392, + 36.03349086086087 + ], + [ + -85.62238969317896, + 36.03317386008855 + ], + [ + -85.62260469297874, + 36.033043860277225 + ], + [ + -85.62308769333583, + 36.032750860120636 + ], + [ + -85.62366469380112, + 36.03240185970359 + ], + [ + -85.6245146937746, + 36.031833859998414 + ], + [ + -85.62472369356531, + 36.03169585958642 + ], + [ + -85.62485469401116, + 36.03160786037119 + ], + [ + -85.6249846933188, + 36.031519859699294 + ], + [ + -85.62643969418784, + 36.031062859930486 + ], + [ + -85.62782569389246, + 36.0305898598311 + ], + [ + -85.62838569469574, + 36.03039985965019 + ], + [ + -85.62863069465881, + 36.03031685936695 + ], + [ + -85.62979569483184, + 36.030026859844405 + ], + [ + -85.62985969476189, + 36.03001285955547 + ], + [ + -85.63127469583135, + 36.02960885928711 + ], + [ + -85.63140569541257, + 36.02957185891784 + ], + [ + -85.63181469538995, + 36.029415859118856 + ], + [ + -85.63322069564934, + 36.02888185874049 + ], + [ + -85.63466769632093, + 36.028577858681814 + ], + [ + -85.63588569619088, + 36.028475858383715 + ], + [ + -85.63605169709297, + 36.02844585891698 + ], + [ + -85.63647769624451, + 36.02836985866569 + ], + [ + -85.63673769653339, + 36.028323858456375 + ], + [ + -85.63751169713156, + 36.02805085842467 + ], + [ + -85.63819569730774, + 36.0276808590047 + ], + [ + -85.63855369775423, + 36.027358858049475 + ], + [ + -85.63878369732824, + 36.02675485826258 + ], + [ + -85.63864869713004, + 36.025705858395604 + ], + [ + -85.63839369682704, + 36.02513185810397 + ], + [ + -85.6382766971018, + 36.02455885833287 + ], + [ + -85.63817969710936, + 36.02405885796839 + ], + [ + -85.63846869746492, + 36.02363985794348 + ], + [ + -85.63868469719299, + 36.023620858069776 + ], + [ + -85.63931069753761, + 36.02356885725256 + ], + [ + -85.64012069746228, + 36.023860857502456 + ], + [ + -85.64059369812406, + 36.02399885817237 + ], + [ + -85.64138669776379, + 36.02422585755837 + ], + [ + -85.6415036981518, + 36.02422585787279 + ], + [ + -85.64280269806801, + 36.02422885730599 + ], + [ + -85.64327869835306, + 36.0241978576859 + ], + [ + -85.64344869822804, + 36.0236488571798 + ], + [ + -85.64330469842699, + 36.02230185742742 + ], + [ + -85.64334269832074, + 36.02148185711759 + ], + [ + -85.64336969859802, + 36.02088985657412 + ], + [ + -85.6434916985572, + 36.019577856791045 + ], + [ + -85.64351369832234, + 36.01934085672293 + ], + [ + -85.64374769876036, + 36.01844885649532 + ], + [ + -85.6439256980202, + 36.01777685641377 + ], + [ + -85.64460869816557, + 36.01505985574841 + ], + [ + -85.64461069867221, + 36.01504985564343 + ], + [ + -85.64471069877035, + 36.01459085577776 + ], + [ + -85.64499069814028, + 36.01330885538356 + ], + [ + -85.64508469862531, + 36.013328855086534 + ], + [ + -85.64554569829001, + 36.01343085568994 + ], + [ + -85.64727669948553, + 36.01380385537831 + ], + [ + -85.6475326991976, + 36.01385985495695 + ], + [ + -85.6480586986128, + 36.013376854918796 + ], + [ + -85.64815769907223, + 36.01343285493442 + ], + [ + -85.64841469908913, + 36.013505855507226 + ], + [ + -85.6486426993493, + 36.01353085530577 + ], + [ + -85.6489986988769, + 36.01349885493772 + ], + [ + -85.64922669908619, + 36.013386855490594 + ], + [ + -85.6494456998637, + 36.01324185519675 + ], + [ + -85.6495246998672, + 36.01300185522522 + ], + [ + -85.64952569959279, + 36.012783854740626 + ], + [ + -85.65105270005442, + 36.012334854795185 + ], + [ + -85.65117770039676, + 36.012452854600504 + ], + [ + -85.65130369950158, + 36.01257085536059 + ], + [ + -85.65152569990495, + 36.01277985477538 + ], + [ + -85.65167470023519, + 36.01278685515174 + ], + [ + -85.65344870110701, + 36.01287385493176 + ], + [ + -85.65344770056619, + 36.012874855177735 + ], + [ + -85.65341270117914, + 36.013073855299766 + ], + [ + -85.65340870089229, + 36.01310185466402 + ], + [ + -85.65329670052348, + 36.01366985501349 + ], + [ + -85.65325870073211, + 36.013868855435064 + ], + [ + -85.65314370049202, + 36.01441885557531 + ], + [ + -85.65285170031379, + 36.01582985597206 + ], + [ + -85.6528217002573, + 36.0160748552738 + ], + [ + -85.65277570093401, + 36.016451855747114 + ], + [ + -85.65273670088358, + 36.0166308554253 + ], + [ + -85.65272170047804, + 36.01667285595073 + ], + [ + -85.65267970118, + 36.01679885535337 + ], + [ + -85.65266570034228, + 36.01684185556694 + ], + [ + -85.65265170067497, + 36.01686985579486 + ], + [ + -85.6526117008746, + 36.01695585586247 + ], + [ + -85.65259870079879, + 36.01698485578076 + ], + [ + -85.65257570070762, + 36.01703085562346 + ], + [ + -85.6525077009425, + 36.01716885620628 + ], + [ + -85.65249670076476, + 36.01719385569619 + ], + [ + -85.65248570037745, + 36.017215855727926 + ], + [ + -85.65539270182855, + 36.017474855855966 + ], + [ + -85.65543270187386, + 36.01751385594828 + ], + [ + -85.66091470320237, + 36.02279485636239 + ], + [ + -85.66185970396718, + 36.023705856797925 + ], + [ + -85.6635037044152, + 36.02528985696119 + ], + [ + -85.66400270453153, + 36.025770856935566 + ], + [ + -85.66535970552783, + 36.02707685689963 + ], + [ + -85.66558570510904, + 36.02729385747009 + ], + [ + -85.66620070480462, + 36.02788485710751 + ], + [ + -85.66637970577386, + 36.028057857193 + ], + [ + -85.66824070555352, + 36.029847857909175 + ], + [ + -85.67077970676631, + 36.03473185838289 + ], + [ + -85.67149670693479, + 36.03611385877383 + ], + [ + -85.6716137076194, + 36.0363398586294 + ], + [ + -85.67313470748513, + 36.03926885946085 + ], + [ + -85.67498770935475, + 36.04283085972553 + ], + [ + -85.67505470917754, + 36.04295986026282 + ], + [ + -85.67536870876734, + 36.04356886029043 + ], + [ + -85.67584570915213, + 36.04448786080179 + ], + [ + -85.67727970970832, + 36.04724786072835 + ], + [ + -85.677758710133, + 36.04816786123792 + ], + [ + -85.6778657102137, + 36.04837386078384 + ], + [ + -85.67808171017877, + 36.04878986113729 + ], + [ + -85.6782057103305, + 36.04898486157337 + ], + [ + -85.67833171069913, + 36.049181860823026 + ], + [ + -85.67837871003319, + 36.04925786161124 + ], + [ + -85.67879670986521, + 36.050079861683756 + ], + [ + -85.68017371038535, + 36.05278486214038 + ], + [ + -85.68063371081759, + 36.05368686235061 + ], + [ + -85.68075871102565, + 36.0539278624972 + ], + [ + -85.68113571114282, + 36.054651862358014 + ], + [ + -85.68126171152565, + 36.05489386227278 + ], + [ + -85.68153371157027, + 36.055386862316624 + ], + [ + -85.68235171207995, + 36.056865862740274 + ], + [ + -85.68262471237439, + 36.057358862694265 + ], + [ + -85.6836647125527, + 36.05923886273383 + ], + [ + -85.68674771305528, + 36.06480886422209 + ], + [ + -85.68677371332606, + 36.06488786394041 + ], + [ + -85.68746071356136, + 36.06692586435455 + ], + [ + -85.68752671379244, + 36.067124864092115 + ], + [ + -85.68754571376327, + 36.067178864117245 + ], + [ + -85.68763071399543, + 36.06743086429778 + ], + [ + -85.6877127139327, + 36.067675864596765 + ], + [ + -85.68772771372734, + 36.06772186448694 + ], + [ + -85.68779571457671, + 36.06792086471232 + ], + [ + -85.6883377147624, + 36.06953086508668 + ], + [ + -85.68842571416435, + 36.069485865188 + ], + [ + -85.68996471529358, + 36.068701864419616 + ], + [ + -85.69354971558151, + 36.069468864465534 + ], + [ + -85.6953077165161, + 36.06984586478846 + ], + [ + -85.69616271615556, + 36.07003086459552 + ], + [ + -85.69702671689319, + 36.07021686422472 + ], + [ + -85.69830571729321, + 36.070503864546296 + ], + [ + -85.69975671784573, + 36.0708298647825 + ], + [ + -85.70194171820252, + 36.07192886447443 + ], + [ + -85.70311371891975, + 36.072518864601754 + ], + [ + -85.7035647188085, + 36.07274586502333 + ], + [ + -85.70358971941758, + 36.072757864664126 + ], + [ + -85.70503871999985, + 36.073446864653654 + ], + [ + -85.70552271961971, + 36.07367686481919 + ], + [ + -85.70732471990209, + 36.07453486505462 + ], + [ + -85.70786972015905, + 36.07479386500102 + ], + [ + -85.71271272206116, + 36.0771458656416 + ], + [ + -85.7145087227603, + 36.07801786592022 + ], + [ + -85.71476572213544, + 36.078142865839666 + ], + [ + -85.71658872295856, + 36.07901786565325 + ], + [ + -85.72106972463988, + 36.081167865853274 + ], + [ + -85.72265272481096, + 36.08199286615497 + ], + [ + -85.72282472567514, + 36.08198986573706 + ], + [ + -85.72513372587754, + 36.08196386542508 + ], + [ + -85.7251597257945, + 36.081962865712235 + ], + [ + -85.72601272571028, + 36.08111686527302 + ], + [ + -85.72695672630996, + 36.08097286517347 + ], + [ + -85.72739072636868, + 36.08090786577488 + ], + [ + -85.72842272669219, + 36.080400865589894 + ], + [ + -85.72943472713096, + 36.07993386561442 + ], + [ + -85.72979172660538, + 36.079731864817006 + ], + [ + -85.72982872718549, + 36.080748865005134 + ], + [ + -85.73078972687303, + 36.080797865036885 + ], + [ + -85.7309767269577, + 36.08169386506184 + ], + [ + -85.73149972778779, + 36.08155286566228 + ], + [ + -85.73361072822287, + 36.0809908656186 + ], + [ + -85.73726372927405, + 36.08002186534252 + ], + [ + -85.7418827303009, + 36.07880186423989 + ], + [ + -85.74158872978647, + 36.07789886404162 + ], + [ + -85.74053873040367, + 36.077719864742505 + ], + [ + -85.74010873019141, + 36.07562186385408 + ], + [ + -85.74167773025871, + 36.07508686400009 + ], + [ + -85.74173472960184, + 36.075066863593186 + ], + [ + -85.7423677303307, + 36.075343863380176 + ], + [ + -85.74583273155261, + 36.076860863579 + ], + [ + -85.74607373190551, + 36.076965863956566 + ], + [ + -85.74679973149931, + 36.07728386427311 + ], + [ + -85.74704173154234, + 36.07738986430058 + ], + [ + -85.74714873145133, + 36.07743686434739 + ], + [ + -85.74739373184511, + 36.077047863465836 + ], + [ + -85.74825173206882, + 36.07568886321863 + ], + [ + -85.74831773154436, + 36.07558586396586 + ], + [ + -85.74862573245191, + 36.07509886319762 + ], + [ + -85.7497757326026, + 36.0733068626198 + ], + [ + -85.7499867318935, + 36.07297886335858 + ], + [ + -85.7530587327768, + 36.06799186224231 + ], + [ + -85.75319373322581, + 36.067957861786674 + ], + [ + -85.75526173333516, + 36.06745286123481 + ], + [ + -85.75526673329657, + 36.067448861655 + ], + [ + -85.75537873304265, + 36.067423861661425 + ], + [ + -85.75694773425766, + 36.06710586130184 + ], + [ + -85.75725173384484, + 36.06719486190117 + ], + [ + -85.75812473490177, + 36.06745086110185 + ], + [ + -85.75905673427035, + 36.067721861700726 + ], + [ + -85.7593447342036, + 36.0678068612525 + ], + [ + -85.76054173485102, + 36.06815786168206 + ], + [ + -85.76167273504878, + 36.06848986163066 + ], + [ + -85.7628027355755, + 36.06882186157349 + ], + [ + -85.76310373616042, + 36.06890986177589 + ], + [ + -85.76392873652799, + 36.06915286156875 + ], + [ + -85.76505473646658, + 36.06948286141071 + ], + [ + -85.76529773624577, + 36.069553861459845 + ], + [ + -85.76602873678759, + 36.06976786204283 + ], + [ + -85.76627273669544, + 36.06983986156224 + ], + [ + -85.76649373654378, + 36.06990386147189 + ], + [ + -85.76714073713732, + 36.07009386129638 + ], + [ + -85.76714373683829, + 36.07011086134059 + ], + [ + -85.76718973666887, + 36.07033686212643 + ], + [ + -85.76736873720917, + 36.071245861845476 + ], + [ + -85.7674447373225, + 36.07163286186855 + ], + [ + -85.76769973740856, + 36.07292886245469 + ], + [ + -85.76789273726189, + 36.073906862419825 + ], + [ + -85.76790573797778, + 36.07397586208469 + ], + [ + -85.76808473755075, + 36.07488586211971 + ], + [ + -85.7685617383502, + 36.07731286300439 + ], + [ + -85.76922673778682, + 36.0774038630314 + ], + [ + -85.77598074039416, + 36.078334863032374 + ], + [ + -85.77521174052349, + 36.08083786382353 + ], + [ + -85.7743257402284, + 36.083731864460425 + ], + [ + -85.77432474032295, + 36.083732863890624 + ], + [ + -85.77428974018683, + 36.083844863959484 + ], + [ + -85.77424974042269, + 36.083868864475754 + ], + [ + -85.77413674018794, + 36.08394486443506 + ], + [ + -85.77409874050562, + 36.08396986454173 + ], + [ + -85.77346873995741, + 36.084378863817285 + ], + [ + -85.7724707398582, + 36.085028864391674 + ], + [ + -85.77211273961201, + 36.08602586479978 + ], + [ + -85.77186073921541, + 36.08673286476736 + ], + [ + -85.77182073987235, + 36.08684486450142 + ], + [ + -85.77147273992895, + 36.08781486532653 + ], + [ + -85.77116773947736, + 36.088669865468965 + ], + [ + -85.7703957394253, + 36.089509865285244 + ], + [ + -85.7702987391828, + 36.09073986584355 + ], + [ + -85.77028473912844, + 36.090906866130474 + ], + [ + -85.77021173988221, + 36.09184586623193 + ], + [ + -85.77017573994435, + 36.09205186621673 + ], + [ + -85.77015673972413, + 36.09215686554244 + ], + [ + -85.77010173997093, + 36.09247486579256 + ], + [ + -85.7700837393854, + 36.092580865598094 + ], + [ + -85.76990673992488, + 36.09431986633154 + ], + [ + -85.76983973981969, + 36.09498786640451 + ], + [ + -85.77351874023987, + 36.09502386634061 + ], + [ + -85.77302474062806, + 36.095769866879 + ], + [ + -85.77206074081106, + 36.09722886674376 + ], + [ + -85.7721757399667, + 36.097421866915276 + ], + [ + -85.77229074055354, + 36.0976358671985 + ], + [ + -85.77254374051148, + 36.09806686690843 + ], + [ + -85.77264174109229, + 36.09823386746634 + ], + [ + -85.77284774030998, + 36.09848986700873 + ], + [ + -85.77295774049846, + 36.09860586694723 + ], + [ + -85.77307274047581, + 36.09870286684697 + ], + [ + -85.77322674080227, + 36.09884286691841 + ], + [ + -85.77335974125067, + 36.0989358672545 + ], + [ + -85.77354274110316, + 36.099041867363276 + ], + [ + -85.77481374162299, + 36.099717867675054 + ], + [ + -85.7748587412368, + 36.09973886719321 + ], + [ + -85.77495674115626, + 36.09978586686924 + ], + [ + -85.77546874150806, + 36.09997286704993 + ], + [ + -85.77548574123738, + 36.09998286751592 + ], + [ + -85.77566374109313, + 36.10008886767768 + ], + [ + -85.7757407410427, + 36.10013286701108 + ], + [ + -85.77587874136364, + 36.100211867376245 + ], + [ + -85.77617674124049, + 36.10038186725134 + ], + [ + -85.77628474179907, + 36.100464867123435 + ], + [ + -85.77629274197848, + 36.10047086721846 + ], + [ + -85.77639274228821, + 36.10058386764554 + ], + [ + -85.77645774205317, + 36.10149686771719 + ], + [ + -85.77659774245117, + 36.102012867348584 + ], + [ + -85.77678674148595, + 36.102343867992396 + ], + [ + -85.77735174223257, + 36.102390867540784 + ], + [ + -85.77794774271747, + 36.10217986805871 + ], + [ + -85.7780847420714, + 36.102131867803635 + ], + [ + -85.77900574263109, + 36.10140586740953 + ], + [ + -85.77946074266865, + 36.100977867483365 + ], + [ + -85.7797877423987, + 36.10064586755298 + ], + [ + -85.78027374318442, + 36.10055686707658 + ], + [ + -85.7807197425863, + 36.1006208669573 + ], + [ + -85.78106774361919, + 36.10097586748952 + ], + [ + -85.78241574343095, + 36.10136086759586 + ], + [ + -85.78346774421752, + 36.10181086722869 + ], + [ + -85.78465874439165, + 36.10217286738338 + ], + [ + -85.78579974428844, + 36.10222086710128 + ], + [ + -85.78608174480871, + 36.10234186755972 + ], + [ + -85.78732674542002, + 36.102879866974476 + ], + [ + -85.78796274527299, + 36.10296786715777 + ], + [ + -85.78820174516999, + 36.103168867316654 + ], + [ + -85.7884987458527, + 36.1038248673917 + ], + [ + -85.78857074600093, + 36.104499867422895 + ], + [ + -85.78842274536159, + 36.10479286781694 + ], + [ + -85.78839274577467, + 36.10485486761245 + ], + [ + -85.78787674523423, + 36.105275868245755 + ], + [ + -85.78884274546326, + 36.107024868060776 + ], + [ + -85.78903574534776, + 36.10723686853971 + ], + [ + -85.78931974641976, + 36.10754886794919 + ], + [ + -85.78985474603105, + 36.10778686862415 + ], + [ + -85.791531746197, + 36.107650868378485 + ], + [ + -85.79187974657417, + 36.10781886778841 + ], + [ + -85.79219374698421, + 36.10828686815253 + ], + [ + -85.79248674684584, + 36.108721868076444 + ], + [ + -85.79295274701042, + 36.1090598683864 + ], + [ + -85.79351974689268, + 36.10965686870172 + ], + [ + -85.79394874703263, + 36.111091868317224 + ], + [ + -85.79433674715142, + 36.11176086891739 + ], + [ + -85.79405174806836, + 36.112882869516305 + ], + [ + -85.79418074737733, + 36.11305186950124 + ], + [ + -85.79508374763029, + 36.11309086947238 + ], + [ + -85.79571874812648, + 36.11317886903562 + ], + [ + -85.79637374864228, + 36.11353286918153 + ], + [ + -85.79640574838238, + 36.113566868955466 + ], + [ + -85.79679074831117, + 36.113967869002614 + ], + [ + -85.79686174915068, + 36.11447586892343 + ], + [ + -85.79675374854892, + 36.114992869089086 + ], + [ + -85.79694274922532, + 36.11550886976258 + ], + [ + -85.79706174824459, + 36.11598186974429 + ], + [ + -85.79709274841883, + 36.11610586956318 + ], + [ + -85.79781774873267, + 36.11665286925566 + ], + [ + -85.79796974874156, + 36.11680787013084 + ], + [ + -85.79797674884695, + 36.11681486927641 + ], + [ + -85.79758574864955, + 36.1198158701483 + ], + [ + -85.79755474889677, + 36.12054087082873 + ], + [ + -85.79750274900613, + 36.121793871109254 + ], + [ + -85.79762974900704, + 36.1217978703982 + ], + [ + -85.79783274976394, + 36.12177887080615 + ], + [ + -85.79790174911297, + 36.12177887106555 + ], + [ + -85.79800074955696, + 36.12180187102683 + ], + [ + -85.79829574992654, + 36.12189387064955 + ], + [ + -85.79854974984264, + 36.121973870300984 + ], + [ + -85.79874074935765, + 36.12199687094494 + ], + [ + -85.79905374929479, + 36.12198487074953 + ], + [ + -85.79930575004563, + 36.12195087053544 + ], + [ + -85.79944275027607, + 36.12195487103337 + ], + [ + -85.79962574965339, + 36.12198887028173 + ], + [ + -85.8001297498763, + 36.12212587036273 + ], + [ + -85.80035074996101, + 36.12216787060001 + ], + [ + -85.80057975066491, + 36.12218687107509 + ], + [ + -85.80068875030987, + 36.12218987033877 + ], + [ + -85.80080074980776, + 36.12219487073219 + ], + [ + -85.8009687501902, + 36.122221871080086 + ], + [ + -85.80147875032652, + 36.12236387105242 + ], + [ + -85.80169775090545, + 36.122434870535606 + ], + [ + -85.80174675094959, + 36.12245087080066 + ], + [ + -85.80237175093946, + 36.122598870811174 + ], + [ + -85.80259675055811, + 36.12265287094917 + ], + [ + -85.80251375058933, + 36.122747870400204 + ], + [ + -85.80038575079887, + 36.12510587113891 + ], + [ + -85.80123475022688, + 36.12547287122251 + ], + [ + -85.80174075033705, + 36.1256508710548 + ], + [ + -85.80229475089999, + 36.12615887132933 + ], + [ + -85.80291875085358, + 36.126602871932285 + ], + [ + -85.80355275112734, + 36.12682887191051 + ], + [ + -85.80373675162073, + 36.12685187122149 + ], + [ + -85.80550975229767, + 36.12708287104228 + ], + [ + -85.80556675211683, + 36.12715887171941 + ], + [ + -85.8056027518843, + 36.12720687180016 + ], + [ + -85.80563575147663, + 36.12726787119094 + ], + [ + -85.80578075238193, + 36.127646871787064 + ], + [ + -85.80583975191173, + 36.1277008714945 + ], + [ + -85.8058897522857, + 36.127762871519394 + ], + [ + -85.80590675243549, + 36.12780487194214 + ], + [ + -85.80592175204326, + 36.12782087165238 + ], + [ + -85.80596575261315, + 36.12786687199308 + ], + [ + -85.80600075223293, + 36.12794887183881 + ], + [ + -85.80604775222895, + 36.12799587195257 + ], + [ + -85.80606775158313, + 36.12802487121677 + ], + [ + -85.80614375202791, + 36.12816087139212 + ], + [ + -85.80616875201454, + 36.12818887197628 + ], + [ + -85.80621275192144, + 36.12826487126411 + ], + [ + -85.80626275170668, + 36.12840887144949 + ], + [ + -85.80630475241662, + 36.12860087201108 + ], + [ + -85.80627475257363, + 36.1286868716786 + ], + [ + -85.80628375276352, + 36.12894487191571 + ], + [ + -85.8062587523889, + 36.12900387181421 + ], + [ + -85.80623275226085, + 36.129102872121095 + ], + [ + -85.8061527518281, + 36.12924787202629 + ], + [ + -85.80610175202298, + 36.12930987183028 + ], + [ + -85.80607675274578, + 36.12937787157826 + ], + [ + -85.80602175227986, + 36.129484871577745 + ], + [ + -85.80600975199157, + 36.12956387214758 + ], + [ + -85.8060087523753, + 36.12959787202785 + ], + [ + -85.8060257526659, + 36.12977687153787 + ], + [ + -85.8060507522307, + 36.12980387174513 + ], + [ + -85.8060687523124, + 36.12983787242597 + ], + [ + -85.80607675173422, + 36.1298658718229 + ], + [ + -85.80611675233827, + 36.129939872017644 + ], + [ + -85.80616875232003, + 36.13000987189937 + ], + [ + -85.80618675276467, + 36.13005787168764 + ], + [ + -85.80621175209923, + 36.13010687228842 + ], + [ + -85.80624575205023, + 36.13014787161588 + ], + [ + -85.80624675283086, + 36.130153871947385 + ], + [ + -85.80630075235756, + 36.13037387191809 + ], + [ + -85.80636375270005, + 36.130565871827315 + ], + [ + -85.80638975228715, + 36.13059387214892 + ], + [ + -85.80643975200927, + 36.13068987247167 + ], + [ + -85.80646575237778, + 36.13072487188848 + ], + [ + -85.80649075212817, + 36.13077887256079 + ], + [ + -85.80655875295075, + 36.13083487176102 + ], + [ + -85.80663775203051, + 36.13088287192737 + ], + [ + -85.80669475237649, + 36.13091687191916 + ], + [ + -85.80676275262516, + 36.130934872480324 + ], + [ + -85.80679675199524, + 36.13094487223198 + ], + [ + -85.80685875296933, + 36.13097887204247 + ], + [ + -85.80709775278635, + 36.131168872105725 + ], + [ + -85.80712575273051, + 36.1311908722334 + ], + [ + -85.80716875251888, + 36.13124287191758 + ], + [ + -85.80719375244118, + 36.131266872515816 + ], + [ + -85.80723875294716, + 36.13133087191458 + ], + [ + -85.80737375246889, + 36.13152387219027 + ], + [ + -85.80741975294785, + 36.13158887260001 + ], + [ + -85.80740275239573, + 36.13160087264619 + ], + [ + -85.80611375218184, + 36.132505872137045 + ], + [ + -85.80589775187872, + 36.13265887251354 + ], + [ + -85.8052677524301, + 36.133361872312484 + ], + [ + -85.80457575248124, + 36.13413387252502 + ], + [ + -85.80391375145082, + 36.135120873522915 + ], + [ + -85.80376675203463, + 36.135553873251645 + ], + [ + -85.80372375177927, + 36.135680873162585 + ], + [ + -85.80368075224561, + 36.13580887344215 + ], + [ + -85.80347875153538, + 36.13640187304249 + ], + [ + -85.80333575196275, + 36.13682487385862 + ], + [ + -85.80306075185412, + 36.137530873866005 + ], + [ + -85.80298975165789, + 36.13771287332801 + ], + [ + -85.80291875198355, + 36.13789587402855 + ], + [ + -85.80284275232873, + 36.13809087384727 + ], + [ + -85.8027657521261, + 36.13829087390405 + ], + [ + -85.80237475160654, + 36.13842787371445 + ], + [ + -85.8021777521382, + 36.13849787351776 + ], + [ + -85.80214975176904, + 36.138505873954664 + ], + [ + -85.80206675144744, + 36.13853087364913 + ], + [ + -85.80203975187061, + 36.13853987428587 + ], + [ + -85.8019367514821, + 36.13858287416078 + ], + [ + -85.8016887518607, + 36.138670873889915 + ], + [ + -85.80162175198038, + 36.138695874340435 + ], + [ + -85.80162075148114, + 36.13929187383729 + ], + [ + -85.80179775140445, + 36.13971187436512 + ], + [ + -85.80194375127677, + 36.14005787446943 + ], + [ + -85.80202275185081, + 36.14021387412752 + ], + [ + -85.80206375199239, + 36.14029487439769 + ], + [ + -85.80209975159622, + 36.140365874310284 + ], + [ + -85.8022527522742, + 36.14061087445736 + ], + [ + -85.80259275156439, + 36.14087687404436 + ], + [ + -85.80280275174941, + 36.14104187457992 + ], + [ + -85.80310275220687, + 36.1412728740385 + ], + [ + -85.80325675241313, + 36.14139987407124 + ], + [ + -85.80370675222915, + 36.1417728746892 + ], + [ + -85.804411752499, + 36.142096874490555 + ], + [ + -85.80475875218993, + 36.142245874698226 + ], + [ + -85.805010752423, + 36.142354874449175 + ], + [ + -85.80512575269293, + 36.14240387452111 + ], + [ + -85.80513175299726, + 36.14243187426427 + ], + [ + -85.80514675261321, + 36.1425068741913 + ], + [ + -85.80516175248843, + 36.142580874631626 + ], + [ + -85.80517075247111, + 36.142629874682505 + ], + [ + -85.80517475331376, + 36.14264587468478 + ], + [ + -85.8051417528237, + 36.14277787491488 + ], + [ + -85.80513075309331, + 36.142827874595135 + ], + [ + -85.80506575261873, + 36.14309287430333 + ], + [ + -85.80487475233522, + 36.14323187503096 + ], + [ + -85.80485675269182, + 36.143288875066986 + ], + [ + -85.80480475311154, + 36.14339187475436 + ], + [ + -85.80476375223338, + 36.143496874958174 + ], + [ + -85.80472175285207, + 36.143538874525255 + ], + [ + -85.80469175278975, + 36.143552874616226 + ], + [ + -85.80463075295458, + 36.14362587514914 + ], + [ + -85.80460075248756, + 36.143673874778614 + ], + [ + -85.80448775271687, + 36.14379687514288 + ], + [ + -85.80435075307794, + 36.14393187534889 + ], + [ + -85.8042767527439, + 36.14402487492649 + ], + [ + -85.80427175236939, + 36.14405287488147 + ], + [ + -85.80425275305412, + 36.1440738746694 + ], + [ + -85.80420175236334, + 36.14423087483641 + ], + [ + -85.80419975272208, + 36.144310875320194 + ], + [ + -85.80422975212616, + 36.14440587491038 + ], + [ + -85.8042507527473, + 36.14442587521428 + ], + [ + -85.80425775267193, + 36.144445875334306 + ], + [ + -85.80435075218168, + 36.144526875257455 + ], + [ + -85.80436775241758, + 36.14454787484579 + ], + [ + -85.80438975262669, + 36.14458387478597 + ], + [ + -85.80440475248577, + 36.144619875090626 + ], + [ + -85.80439175270762, + 36.14465287481626 + ], + [ + -85.80434275308906, + 36.14471387470268 + ], + [ + -85.80432375300835, + 36.144732874629575 + ], + [ + -85.80422375303135, + 36.14481987474679 + ], + [ + -85.80418175257716, + 36.14482587469185 + ], + [ + -85.80414675302839, + 36.14484787501302 + ], + [ + -85.80406275296147, + 36.14488787478504 + ], + [ + -85.80397975262109, + 36.144910874994906 + ], + [ + -85.80424775224058, + 36.145083875470064 + ], + [ + -85.80505475338676, + 36.14560487527964 + ], + [ + -85.80532375307979, + 36.145778875325796 + ], + [ + -85.80574075286975, + 36.146047875627055 + ], + [ + -85.80597975293475, + 36.14622387532133 + ], + [ + -85.80614675363077, + 36.14634687552397 + ], + [ + -85.8062247529638, + 36.14646787512894 + ], + [ + -85.80652175348054, + 36.146926875411836 + ], + [ + -85.8065867529796, + 36.14700587565058 + ], + [ + -85.80665275359125, + 36.147085875136625 + ], + [ + -85.80698375407063, + 36.147486875778576 + ], + [ + -85.80725375406784, + 36.147813875833485 + ], + [ + -85.80707175394411, + 36.14941487612526 + ], + [ + -85.8066747540782, + 36.152925876975566 + ], + [ + -85.80556875454486, + 36.162687878792106 + ], + [ + -85.80528875467395, + 36.16516087885749 + ], + [ + -85.80451475397749, + 36.16520087939227 + ], + [ + -85.80431275405884, + 36.166372879830114 + ], + [ + -85.80405175384382, + 36.16790487945414 + ], + [ + -85.80363575378041, + 36.17035088010355 + ], + [ + -85.80145375337622, + 36.17030388073204 + ], + [ + -85.79990075290219, + 36.17027188076261 + ], + [ + -85.79978875280585, + 36.17026888046359 + ], + [ + -85.79945275276356, + 36.17026288007665 + ], + [ + -85.799340752388, + 36.17026088064048 + ], + [ + -85.79921675306689, + 36.17025888006746 + ], + [ + -85.79714075186092, + 36.17058188084647 + ], + [ + -85.7905507501889, + 36.17160888068266 + ], + [ + -85.78835575033915, + 36.171952881436745 + ], + [ + -85.7881257493045, + 36.1719868810119 + ], + [ + -85.78743574959718, + 36.17209588155919 + ], + [ + -85.78720674945525, + 36.172131880999665 + ], + [ + -85.7857397494725, + 36.17236088142347 + ], + [ + -85.78535074944632, + 36.17291688141845 + ], + [ + -85.78531874885344, + 36.172920881895365 + ], + [ + -85.78460474882263, + 36.17301088128582 + ], + [ + -85.78396074887831, + 36.173092881577695 + ], + [ + -85.78373674870276, + 36.17482588164707 + ], + [ + -85.78416974842519, + 36.17601888214929 + ], + [ + -85.78602574919361, + 36.17626488218878 + ], + [ + -85.78717674941771, + 36.176416882382696 + ], + [ + -85.78700975041316, + 36.17845388259507 + ], + [ + -85.78686775000159, + 36.1802048831528 + ], + [ + -85.78781075046331, + 36.180325882823475 + ], + [ + -85.78801774984255, + 36.180338882762435 + ], + [ + -85.79066875067696, + 36.18048988261169 + ], + [ + -85.79083775116327, + 36.18021888279938 + ], + [ + -85.79125775066996, + 36.17954788297291 + ], + [ + -85.79289575131946, + 36.179717882288934 + ], + [ + -85.79362175138776, + 36.1790658822862 + ], + [ + -85.79291975178026, + 36.17828388242368 + ], + [ + -85.79300675146041, + 36.17382488114734 + ], + [ + -85.79301375092642, + 36.17351088112312 + ], + [ + -85.79375775182366, + 36.17344688136345 + ], + [ + -85.79425375197802, + 36.1735448815391 + ], + [ + -85.79554975257854, + 36.17409588134395 + ], + [ + -85.79579075207371, + 36.174198881087676 + ], + [ + -85.7963977526533, + 36.173747881220145 + ], + [ + -85.79778075258187, + 36.172724880889575 + ], + [ + -85.79871675321412, + 36.17177188085197 + ], + [ + -85.79878675273105, + 36.17170188101843 + ], + [ + -85.79968775320663, + 36.17188288063992 + ], + [ + -85.80040375292519, + 36.17202588056517 + ], + [ + -85.80018975313779, + 36.172881880905095 + ], + [ + -85.7998107534106, + 36.1744108814125 + ], + [ + -85.80305675439755, + 36.174735880988145 + ], + [ + -85.80305675388023, + 36.17473788083903 + ], + [ + -85.8032307542627, + 36.176341881304545 + ], + [ + -85.80336075497115, + 36.17754188178605 + ], + [ + -85.80360775479474, + 36.17981488174512 + ], + [ + -85.80493975564994, + 36.17991388218779 + ], + [ + -85.80524575473493, + 36.17993688197534 + ], + [ + -85.80491575564893, + 36.18075088233887 + ], + [ + -85.80491475528454, + 36.18077388213288 + ], + [ + -85.80490975559829, + 36.180860882326655 + ], + [ + -85.80489575564087, + 36.181122882180006 + ], + [ + -85.80489175507786, + 36.18121088255175 + ], + [ + -85.80487775543799, + 36.18145688269362 + ], + [ + -85.80483975564405, + 36.18219588264059 + ], + [ + -85.80482675484991, + 36.182442882897476 + ], + [ + -85.80479475503141, + 36.183050882971465 + ], + [ + -85.80438775519208, + 36.18302888286649 + ], + [ + -85.80134575436459, + 36.18286488301285 + ], + [ + -85.80049175455564, + 36.18281888324579 + ], + [ + -85.80042475423282, + 36.18296388265461 + ], + [ + -85.80032975400339, + 36.183173882571026 + ], + [ + -85.80004675361073, + 36.183803883315036 + ], + [ + -85.79995275384915, + 36.184014883118486 + ], + [ + -85.79985575419863, + 36.1842368828789 + ], + [ + -85.79956875422681, + 36.184902883749835 + ], + [ + -85.79947375435034, + 36.18512588324163 + ], + [ + -85.79938975394674, + 36.185270883493445 + ], + [ + -85.79895475402941, + 36.1857138832633 + ], + [ + -85.7986567539139, + 36.18606788387386 + ], + [ + -85.79848875356679, + 36.186251884083894 + ], + [ + -85.79824475324193, + 36.18647288353909 + ], + [ + -85.79824275398573, + 36.18647388327515 + ], + [ + -85.798023753515, + 36.18666388363573 + ], + [ + -85.79777175345923, + 36.18679688364681 + ], + [ + -85.79760375378778, + 36.186838883664876 + ], + [ + -85.79743575353254, + 36.186861883565435 + ], + [ + -85.79726075337877, + 36.18686188362035 + ], + [ + -85.79699375289573, + 36.18683488360549 + ], + [ + -85.79682575332173, + 36.18683888367694 + ], + [ + -85.79665775283632, + 36.18686188429735 + ], + [ + -85.79648975337908, + 36.18691088357672 + ], + [ + -85.79620775306105, + 36.187086883647815 + ], + [ + -85.79583375257864, + 36.18736188431915 + ], + [ + -85.79551375308979, + 36.18763588389321 + ], + [ + -85.79546775350246, + 36.18770488398952 + ], + [ + -85.79544475284992, + 36.18778088419129 + ], + [ + -85.79545275259392, + 36.18786088433128 + ], + [ + -85.7956277535967, + 36.18831888378645 + ], + [ + -85.79572775287028, + 36.188704884221956 + ], + [ + -85.79574275316438, + 36.188764884203856 + ], + [ + -85.79584175299348, + 36.1891888843497 + ], + [ + -85.79586475296745, + 36.189413884519105 + ], + [ + -85.79584975268685, + 36.18973788434001 + ], + [ + -85.7958127530524, + 36.18995688442376 + ], + [ + -85.79578075265441, + 36.19015788443346 + ], + [ + -85.79565875338021, + 36.19062688509339 + ], + [ + -85.79552175364702, + 36.1910848849137 + ], + [ + -85.79549075351471, + 36.191267885189404 + ], + [ + -85.79548275358253, + 36.191450884756236 + ], + [ + -85.7955177534648, + 36.191693884837115 + ], + [ + -85.7955217535686, + 36.19174488444753 + ], + [ + -85.7956117534657, + 36.19173688515645 + ], + [ + -85.79889975408845, + 36.19145588458911 + ], + [ + -85.79933375438944, + 36.191418884549705 + ], + [ + -85.79971075455958, + 36.191757884999284 + ], + [ + -85.80019375486769, + 36.1927898846901 + ], + [ + -85.8006277554063, + 36.19393488554627 + ], + [ + -85.80083275478464, + 36.195038885563534 + ], + [ + -85.80051275476096, + 36.195691885167115 + ], + [ + -85.80019375529753, + 36.19630488596499 + ], + [ + -85.79978375535374, + 36.197053885381195 + ], + [ + -85.7995937552431, + 36.197770886175036 + ], + [ + -85.7996207549164, + 36.19843988570439 + ], + [ + -85.7997587548143, + 36.19901288591907 + ], + [ + -85.79946875461053, + 36.19947188622923 + ], + [ + -85.79923975460709, + 36.19989088592081 + ], + [ + -85.79917075519074, + 36.20033488661168 + ], + [ + -85.79912775501376, + 36.2006158864685 + ], + [ + -85.79880775512075, + 36.20135788642861 + ], + [ + -85.79875575493035, + 36.20217188665999 + ], + [ + -85.79899175473061, + 36.20363588690381 + ], + [ + -85.79901375509098, + 36.203633886736796 + ], + [ + -85.79906175534866, + 36.20363088710613 + ], + [ + -85.79908775456971, + 36.203624886697845 + ], + [ + -85.79931275474237, + 36.2035778875303 + ], + [ + -85.79937075476555, + 36.20355788731403 + ], + [ + -85.79946375562548, + 36.203527887439854 + ], + [ + -85.79946275479811, + 36.20356088712753 + ], + [ + -85.79946275550816, + 36.20366088703852 + ], + [ + -85.79946275517148, + 36.20369488695262 + ], + [ + -85.7994587556581, + 36.205794887663515 + ], + [ + -85.79945875501534, + 36.20631588738553 + ], + [ + -85.80108775589562, + 36.206284887249424 + ], + [ + -85.80175475555043, + 36.2058338871899 + ], + [ + -85.80242175580084, + 36.20529388759605 + ], + [ + -85.80331775579619, + 36.20460188694895 + ], + [ + -85.80399375632439, + 36.20423888718108 + ], + [ + -85.80457675668114, + 36.20417588690642 + ], + [ + -85.80666575757378, + 36.203950887061914 + ], + [ + -85.80682775762256, + 36.20437488701036 + ], + [ + -85.80688375733224, + 36.20452188741085 + ], + [ + -85.80728075753473, + 36.20489088693501 + ], + [ + -85.80783475720229, + 36.20526088667686 + ], + [ + -85.80783675752619, + 36.20526188662306 + ], + [ + -85.80827275734681, + 36.20539188666505 + ], + [ + -85.80854175801628, + 36.205470887398555 + ], + [ + -85.80873075810082, + 36.205525887199805 + ], + [ + -85.8089097574621, + 36.205577886898986 + ], + [ + -85.80914775844505, + 36.2057968869094 + ], + [ + -85.80935375806297, + 36.206217886761216 + ], + [ + -85.80917275776798, + 36.20660488734193 + ], + [ + -85.80914575827092, + 36.20661888767151 + ], + [ + -85.80872575857167, + 36.20684788771678 + ], + [ + -85.80870475759927, + 36.20687788710589 + ], + [ + -85.80847575835489, + 36.20712188777108 + ], + [ + -85.80815575803668, + 36.20740388719286 + ], + [ + -85.8080217574056, + 36.2075048870848 + ], + [ + -85.80771275821884, + 36.20773988733642 + ], + [ + -85.80753775799607, + 36.20788088745373 + ], + [ + -85.80671375730856, + 36.20853688743768 + ], + [ + -85.80637775732495, + 36.20883688828629 + ], + [ + -85.80593575749211, + 36.209211887799576 + ], + [ + -85.80577375731173, + 36.20932988785103 + ], + [ + -85.80556175772871, + 36.20947188809287 + ], + [ + -85.8054927574591, + 36.20951688811513 + ], + [ + -85.80540875733591, + 36.20960088814779 + ], + [ + -85.80536275769077, + 36.20966188776322 + ], + [ + -85.80524075704814, + 36.2098868880486 + ], + [ + -85.80515075719929, + 36.2100398881988 + ], + [ + -85.80514475762469, + 36.2100488881274 + ], + [ + -85.80512875731121, + 36.21007588843221 + ], + [ + -85.8051237572641, + 36.21008588853675 + ], + [ + -85.80485875741786, + 36.21072888829775 + ], + [ + -85.80456975694102, + 36.211002888180204 + ], + [ + -85.80425175654484, + 36.21110288835499 + ], + [ + -85.80393475710054, + 36.211203888781114 + ], + [ + -85.8033277570275, + 36.211251888203485 + ], + [ + -85.80280175663424, + 36.21116288826213 + ], + [ + -85.80234475625245, + 36.21131588847489 + ], + [ + -85.80161875581237, + 36.211395888173605 + ], + [ + -85.80107275615514, + 36.211467888813004 + ], + [ + -85.80058675553376, + 36.21124988882867 + ], + [ + -85.80009475611094, + 36.211108888938995 + ], + [ + -85.8000807559421, + 36.21110488863109 + ], + [ + -85.79973275620543, + 36.21106388822844 + ], + [ + -85.79932575535787, + 36.21113588879178 + ], + [ + -85.7991067551322, + 36.21139388870374 + ], + [ + -85.79883775581226, + 36.21154688902786 + ], + [ + -85.79737575514537, + 36.21143488858692 + ], + [ + -85.79299075329602, + 36.211100888899715 + ], + [ + -85.79152975383909, + 36.210989889123155 + ], + [ + -85.79143775358496, + 36.21096688856913 + ], + [ + -85.79116375298392, + 36.21089988887485 + ], + [ + -85.79107275341558, + 36.210877889237715 + ], + [ + -85.7901007524391, + 36.21084188937635 + ], + [ + -85.78718775151633, + 36.21073688888632 + ], + [ + -85.78621675166733, + 36.21070288909404 + ], + [ + -85.78508175151946, + 36.21115288897472 + ], + [ + -85.78450475139742, + 36.21138588890939 + ], + [ + -85.78448875091318, + 36.21138488973127 + ], + [ + -85.78337175083416, + 36.21133688971415 + ], + [ + -85.78294375141908, + 36.21143288913811 + ], + [ + -85.78175275099034, + 36.213633889431264 + ], + [ + -85.78148175087624, + 36.21431888989053 + ], + [ + -85.78155875103174, + 36.21502888971542 + ], + [ + -85.78157775057284, + 36.215092889808744 + ], + [ + -85.78184375110823, + 36.215964890024935 + ], + [ + -85.78230475147889, + 36.21679289064034 + ], + [ + -85.78251375077232, + 36.21763589095225 + ], + [ + -85.7829267517927, + 36.218220890321206 + ], + [ + -85.7831067509187, + 36.218474890890164 + ], + [ + -85.7831317513215, + 36.21981489078535 + ], + [ + -85.78015675064935, + 36.22070389125162 + ], + [ + -85.77863875049545, + 36.22115889165452 + ], + [ + -85.77866175058486, + 36.22120989109045 + ], + [ + -85.77873874979461, + 36.221298891535916 + ], + [ + -85.77889875071267, + 36.22151189166689 + ], + [ + -85.77891575056265, + 36.221552891745844 + ], + [ + -85.77900875005176, + 36.22164989176294 + ], + [ + -85.77905175043287, + 36.22170389184993 + ], + [ + -85.77906875043656, + 36.22173889152412 + ], + [ + -85.77927975008333, + 36.221909891605435 + ], + [ + -85.77955375049314, + 36.22211189141506 + ], + [ + -85.77958875044197, + 36.22213689138486 + ], + [ + -85.77982075110421, + 36.22229489164614 + ], + [ + -85.78003375091994, + 36.2224668916836 + ], + [ + -85.78009275023818, + 36.222538891918866 + ], + [ + -85.78025375115843, + 36.22269989200655 + ], + [ + -85.78032075045083, + 36.222789891594516 + ], + [ + -85.78038075062048, + 36.22289989199674 + ], + [ + -85.78041475135336, + 36.222981891700144 + ], + [ + -85.7804907513319, + 36.223098892128924 + ], + [ + -85.78120975073564, + 36.22373089153443 + ], + [ + -85.78127075073284, + 36.2237738923642 + ], + [ + -85.78129475106735, + 36.22380389174712 + ], + [ + -85.78142975071074, + 36.22389589195413 + ], + [ + -85.78160775103741, + 36.2240538918969 + ], + [ + -85.78168675169297, + 36.224140892370826 + ], + [ + -85.78175275171469, + 36.22423289184497 + ], + [ + -85.78182375155846, + 36.224322892375376 + ], + [ + -85.7818227509704, + 36.22435789246256 + ], + [ + -85.78185275177952, + 36.22438289173948 + ], + [ + -85.78191175145919, + 36.22443889244228 + ], + [ + -85.7820047516991, + 36.22454789171412 + ], + [ + -85.78208175200265, + 36.22467789219967 + ], + [ + -85.78210675149862, + 36.22473389183847 + ], + [ + -85.78213275186123, + 36.224849892524304 + ], + [ + -85.78214075102575, + 36.2249528920652 + ], + [ + -85.78211875141183, + 36.2250348920574 + ], + [ + -85.7821147515306, + 36.22504989208839 + ], + [ + -85.78208075160641, + 36.225090892495764 + ], + [ + -85.78193775096713, + 36.225207892270205 + ], + [ + -85.7818787511754, + 36.2252418918078 + ], + [ + -85.78161175169045, + 36.22535889251855 + ], + [ + -85.78159075180677, + 36.22537289207262 + ], + [ + -85.78150575116872, + 36.2253868921282 + ], + [ + -85.78147675148074, + 36.22539689196216 + ], + [ + -85.78138775086123, + 36.22540689225919 + ], + [ + -85.78128575114646, + 36.22543489260784 + ], + [ + -85.78121775085529, + 36.22547589248292 + ], + [ + -85.78119275173174, + 36.22549689267075 + ], + [ + -85.78115075103828, + 36.22554489257436 + ], + [ + -85.78114975149359, + 36.22556489261176 + ], + [ + -85.78117575090256, + 36.22568889187218 + ], + [ + -85.78122175137021, + 36.22573689257373 + ], + [ + -85.78123475120877, + 36.22575089235769 + ], + [ + -85.78126875137329, + 36.22581289260585 + ], + [ + -85.78132375108818, + 36.22598089270179 + ], + [ + -85.78139575153801, + 36.226307892864455 + ], + [ + -85.78142875115728, + 36.2265138920803 + ], + [ + -85.78143575136409, + 36.226711892703605 + ], + [ + -85.78144975173826, + 36.2271018926422 + ], + [ + -85.78146775107304, + 36.227334892464576 + ], + [ + -85.78149275169535, + 36.227485892355276 + ], + [ + -85.78153075100458, + 36.22767489255701 + ], + [ + -85.78169975214071, + 36.22806689303768 + ], + [ + -85.78181075138134, + 36.22823789323621 + ], + [ + -85.78181875216674, + 36.22826589258937 + ], + [ + -85.78192875128065, + 36.228505892403774 + ], + [ + -85.7819757512154, + 36.228647892549 + ], + [ + -85.78203575185452, + 36.22890989285716 + ], + [ + -85.78211475139351, + 36.2291728925999 + ], + [ + -85.78214075192372, + 36.229199893386166 + ], + [ + -85.7821907520255, + 36.22928989263845 + ], + [ + -85.78225475136836, + 36.22936689257456 + ], + [ + -85.78238575232965, + 36.229495893213404 + ], + [ + -85.7823957523613, + 36.229508893398595 + ], + [ + -85.78242775205668, + 36.22955089322793 + ], + [ + -85.78245375193343, + 36.22959889342489 + ], + [ + -85.7824867523418, + 36.22970189276256 + ], + [ + -85.7824867522181, + 36.22974289316673 + ], + [ + -85.78243975207202, + 36.23011689325934 + ], + [ + -85.78244475166876, + 36.23031289289293 + ], + [ + -85.78246175242406, + 36.230367892917506 + ], + [ + -85.78250475215667, + 36.23040889323717 + ], + [ + -85.78252075180664, + 36.23043389322592 + ], + [ + -85.7825457516005, + 36.23045689337087 + ], + [ + -85.78257175251952, + 36.23046489349715 + ], + [ + -85.78262175203366, + 36.23045089280774 + ], + [ + -85.78269875189213, + 36.23040289323739 + ], + [ + -85.78283475243248, + 36.23038189320019 + ], + [ + -85.78310975199365, + 36.23030989351754 + ], + [ + -85.78326575250084, + 36.230284893210964 + ], + [ + -85.78330375171646, + 36.230274892881184 + ], + [ + -85.78334775174743, + 36.23026789357154 + ], + [ + -85.78337575274679, + 36.23026489313715 + ], + [ + -85.78343975208219, + 36.23024689323755 + ], + [ + -85.7837107522448, + 36.23020689297555 + ], + [ + -85.78412075229944, + 36.23016089329402 + ], + [ + -85.78431575256316, + 36.23014789266017 + ], + [ + -85.78445075296845, + 36.2301478930022 + ], + [ + -85.78461175294709, + 36.230167892709204 + ], + [ + -85.78471375218376, + 36.230184893225484 + ], + [ + -85.78476475266278, + 36.23020189291956 + ], + [ + -85.7848747530887, + 36.23027389318376 + ], + [ + -85.78501775284155, + 36.23034689295966 + ], + [ + -85.78509375264835, + 36.23035989319941 + ], + [ + -85.78521375282389, + 36.230394893066276 + ], + [ + -85.78523775326723, + 36.23040889303171 + ], + [ + -85.78534375295884, + 36.230439893468386 + ], + [ + -85.78549175314708, + 36.23045589268732 + ], + [ + -85.78554275313388, + 36.230483893320454 + ], + [ + -85.78564475330083, + 36.230497892867035 + ], + [ + -85.78570775337073, + 36.23050089307493 + ], + [ + -85.78577275301954, + 36.23049789282604 + ], + [ + -85.78579675295724, + 36.230496892667276 + ], + [ + -85.78585575276405, + 36.23048389344264 + ], + [ + -85.78621175296453, + 36.23021589276501 + ], + [ + -85.78627975297555, + 36.23017389337485 + ], + [ + -85.78631275297583, + 36.23014689333307 + ], + [ + -85.78646575348796, + 36.23011889331886 + ], + [ + -85.7864917526971, + 36.23011689258996 + ], + [ + -85.78500275260018, + 36.23173389309748 + ], + [ + -85.78384975254389, + 36.23298789388768 + ], + [ + -85.78289175236547, + 36.234156894154054 + ], + [ + -85.78234275262484, + 36.23463189385246 + ], + [ + -85.7813667523497, + 36.235033893873386 + ], + [ + -85.78045975206675, + 36.23553289400169 + ], + [ + -85.78019675122758, + 36.23602889436207 + ], + [ + -85.78016975192756, + 36.236081894043565 + ], + [ + -85.7799387512564, + 36.236782895038104 + ], + [ + -85.77972775217071, + 36.23732289479105 + ], + [ + -85.77989575135814, + 36.23768689470826 + ], + [ + -85.78015275186024, + 36.238017894889886 + ], + [ + -85.78289075266015, + 36.24466689566531 + ], + [ + -85.78268975351433, + 36.24517489598238 + ], + [ + -85.78187275248324, + 36.24575489612099 + ], + [ + -85.78116575229019, + 36.24619689651082 + ], + [ + -85.78075675214176, + 36.2465108961104 + ], + [ + -85.7805477529362, + 36.24677689634687 + ], + [ + -85.77977275274156, + 36.248570897053305 + ], + [ + -85.77974975268063, + 36.248624896890995 + ], + [ + -85.77967375278186, + 36.24880089670527 + ], + [ + -85.77964875208613, + 36.24886089681095 + ], + [ + -85.77962575182531, + 36.24889689742128 + ], + [ + -85.77861275207341, + 36.24905489705675 + ], + [ + -85.77762875156522, + 36.24908589725999 + ], + [ + -85.77723175192087, + 36.24890789745746 + ], + [ + -85.77720875168207, + 36.24884689731342 + ], + [ + -85.77689675102894, + 36.24799689658731 + ], + [ + -85.7756757512337, + 36.245703896401025 + ], + [ + -85.77560375084096, + 36.24562189653869 + ], + [ + -85.77556975074782, + 36.2455548968787 + ], + [ + -85.77551575080457, + 36.24550089623541 + ], + [ + -85.77548075122775, + 36.24547689653007 + ], + [ + -85.77545875039615, + 36.24544489648625 + ], + [ + -85.77543775113226, + 36.24537589680012 + ], + [ + -85.77543575057196, + 36.24528089609852 + ], + [ + -85.77542975072828, + 36.24527489632176 + ], + [ + -85.77457475060507, + 36.24525389626596 + ], + [ + -85.77448575029955, + 36.24526189615658 + ], + [ + -85.77402075014082, + 36.24519289668933 + ], + [ + -85.77383475011214, + 36.245192896528124 + ], + [ + -85.77379175026694, + 36.24522789612444 + ], + [ + -85.77367375089024, + 36.245275896866005 + ], + [ + -85.7735817502226, + 36.24533089644619 + ], + [ + -85.77346675011218, + 36.245385896994776 + ], + [ + -85.77341175030762, + 36.245419897028974 + ], + [ + -85.77338675052893, + 36.24540589651017 + ], + [ + -85.77334375031657, + 36.24541289675745 + ], + [ + -85.77328875055616, + 36.24540989703696 + ], + [ + -85.77325875053324, + 36.24539289685191 + ], + [ + -85.77311575029411, + 36.24535089638189 + ], + [ + -85.77262475067397, + 36.245309896879625 + ], + [ + -85.77239574954494, + 36.24531089684081 + ], + [ + -85.77227675050696, + 36.24532389625076 + ], + [ + -85.77215874960255, + 36.24535889627339 + ], + [ + -85.77207474958034, + 36.24540689647862 + ], + [ + -85.77203174990049, + 36.245412896804154 + ], + [ + -85.77160875027698, + 36.245646897095455 + ], + [ + -85.77157375038796, + 36.245653896515016 + ], + [ + -85.77154975034513, + 36.24567489649001 + ], + [ + -85.77149775010525, + 36.245681897087 + ], + [ + -85.77144775033044, + 36.24571589667014 + ], + [ + -85.77124375001353, + 36.24577089704082 + ], + [ + -85.77085874920469, + 36.24582889656398 + ], + [ + -85.77082975003474, + 36.24583989686106 + ], + [ + -85.77075774994195, + 36.245849897224375 + ], + [ + -85.77052474979689, + 36.24590889685563 + ], + [ + -85.77045274960297, + 36.245918897037015 + ], + [ + -85.77042374923117, + 36.24592989723419 + ], + [ + -85.77023274953167, + 36.2459598972512 + ], + [ + -85.77009774942238, + 36.24597389688571 + ], + [ + -85.7698477494742, + 36.245956897003225 + ], + [ + -85.76967774886717, + 36.245907897112986 + ], + [ + -85.7693907494209, + 36.245777897135255 + ], + [ + -85.76915474957677, + 36.245605896905666 + ], + [ + -85.76909874881838, + 36.245553896587275 + ], + [ + -85.76907774868872, + 36.245524896559274 + ], + [ + -85.76903474948789, + 36.24548289669376 + ], + [ + -85.76900174954453, + 36.2454208970683 + ], + [ + -85.76897574927155, + 36.24539389718823 + ], + [ + -85.76877274952118, + 36.244933896901784 + ], + [ + -85.76866274898606, + 36.24466589693392 + ], + [ + -85.76854474834352, + 36.24445289668084 + ], + [ + -85.76848574847469, + 36.24438289649268 + ], + [ + -85.76821374932177, + 36.24416389686346 + ], + [ + -85.76811274837695, + 36.24410189626832 + ], + [ + -85.76787574831141, + 36.24389189685121 + ], + [ + -85.7670767479717, + 36.24321389636585 + ], + [ + -85.76703774873472, + 36.243180896777076 + ], + [ + -85.76702074856016, + 36.24314089653822 + ], + [ + -85.76699574838258, + 36.24311289597945 + ], + [ + -85.76697874831733, + 36.24291389647788 + ], + [ + -85.76701674817997, + 36.24268989675092 + ], + [ + -85.76707974863609, + 36.242534896015634 + ], + [ + -85.76710074854098, + 36.242263896191105 + ], + [ + -85.76709674852567, + 36.24218489622336 + ], + [ + -85.7670797483403, + 36.24206089652227 + ], + [ + -85.76699574862216, + 36.24195789590735 + ], + [ + -85.76675374863105, + 36.24176589635626 + ], + [ + -85.76637774794875, + 36.24155989630055 + ], + [ + -85.76589574745805, + 36.24130989653134 + ], + [ + -85.76570474838341, + 36.241188895645 + ], + [ + -85.7653107472056, + 36.240900896391686 + ], + [ + -85.76493074727095, + 36.24059089624725 + ], + [ + -85.76479474778613, + 36.240473895983584 + ], + [ + -85.76477874724546, + 36.2404398958889 + ], + [ + -85.76474474737245, + 36.240396896266134 + ], + [ + -85.76473574715186, + 36.24038489606261 + ], + [ + -85.76462174750729, + 36.2402538955588 + ], + [ + -85.764536747199, + 36.240144896276654 + ], + [ + -85.76448974794181, + 36.24009589612426 + ], + [ + -85.76447374763377, + 36.24005589563397 + ], + [ + -85.7644317477387, + 36.239999895480935 + ], + [ + -85.76432074755083, + 36.23989689615877 + ], + [ + -85.76409274732526, + 36.239718895536846 + ], + [ + -85.76404174732959, + 36.239704895453606 + ], + [ + -85.76399974777206, + 36.23966389602509 + ], + [ + -85.76395674752729, + 36.23960889590695 + ], + [ + -85.76389774764048, + 36.23956089585864 + ], + [ + -85.76388174766876, + 36.23952689563611 + ], + [ + -85.7638597468117, + 36.23949889575034 + ], + [ + -85.76380174699528, + 36.23943789564229 + ], + [ + -85.76376174761896, + 36.2393958960738 + ], + [ + -85.7636867475127, + 36.239333895650375 + ], + [ + -85.76365274665402, + 36.23927889589765 + ], + [ + -85.76350474750808, + 36.23913689591749 + ], + [ + -85.76348274653924, + 36.23910789559987 + ], + [ + -85.76337374662495, + 36.23899789587555 + ], + [ + -85.76331474745855, + 36.238914895573664 + ], + [ + -85.7632207474497, + 36.23883889585306 + ], + [ + -85.7632037466728, + 36.238804895373676 + ], + [ + -85.76314474716378, + 36.238735895508526 + ], + [ + -85.76305974701826, + 36.23866789544965 + ], + [ + -85.7630427474569, + 36.23863389584935 + ], + [ + -85.76301774718048, + 36.238603895508454 + ], + [ + -85.76298374675584, + 36.238563895539826 + ], + [ + -85.76289974705763, + 36.23849589552467 + ], + [ + -85.76288174713177, + 36.238460895379475 + ], + [ + -85.76286574710424, + 36.238440895537096 + ], + [ + -85.76275174680961, + 36.23833289604407 + ], + [ + -85.76269574661188, + 36.23826189571232 + ], + [ + -85.76253474642786, + 36.23811089546023 + ], + [ + -85.76231974667223, + 36.23793889566145 + ], + [ + -85.76222274662574, + 36.237883895888466 + ], + [ + -85.76208574660855, + 36.23779289534347 + ], + [ + -85.7620637463732, + 36.23778989599743 + ], + [ + -85.76196874637053, + 36.237739895446616 + ], + [ + -85.76184874638867, + 36.237694895103616 + ], + [ + -85.76177374678079, + 36.237671895771555 + ], + [ + -85.76165974696065, + 36.237654895515384 + ], + [ + -85.76157874610097, + 36.2376298952183 + ], + [ + -85.76145974672087, + 36.2376088951828 + ], + [ + -85.76140174689067, + 36.23758889511589 + ], + [ + -85.7612767466702, + 36.23757389512001 + ], + [ + -85.76116374645879, + 36.23754089511932 + ], + [ + -85.76113074591578, + 36.237519895316346 + ], + [ + -85.76104574652423, + 36.23749989576304 + ], + [ + -85.76101974574416, + 36.23748589516997 + ], + [ + -85.76096174614504, + 36.23747289560758 + ], + [ + -85.76092674624937, + 36.237450895938515 + ], + [ + -85.76089374618326, + 36.23744489516013 + ], + [ + -85.76080874574518, + 36.237396895274806 + ], + [ + -85.76077474638765, + 36.237389895730274 + ], + [ + -85.7607537465664, + 36.23737689542861 + ], + [ + -85.76049474603789, + 36.23725889581929 + ], + [ + -85.76041874589593, + 36.237217895290414 + ], + [ + -85.76038574564573, + 36.23721089581469 + ], + [ + -85.76036474593573, + 36.23719789544476 + ], + [ + -85.76019874634324, + 36.23712189558192 + ], + [ + -85.76002974568651, + 36.237038895118594 + ], + [ + -85.76002574574873, + 36.23703689546541 + ], + [ + -85.75953074587362, + 36.23681289577591 + ], + [ + -85.75929374599993, + 36.23669589535855 + ], + [ + -85.75907374520709, + 36.23657889502817 + ], + [ + -85.75900574571764, + 36.2365588954888 + ], + [ + -85.7589807454196, + 36.23655889518115 + ], + [ + -85.7589207458157, + 36.23660689548872 + ], + [ + -85.75885374573207, + 36.23664089567626 + ], + [ + -85.75872274573065, + 36.236727895254845 + ], + [ + -85.75865874536527, + 36.236771895913215 + ], + [ + -85.75857474538081, + 36.236778895518654 + ], + [ + -85.75851474573396, + 36.23681289508717 + ], + [ + -85.7584807453416, + 36.236819895428 + ], + [ + -85.75844774578098, + 36.236840895591655 + ], + [ + -85.75843574568906, + 36.236844895568005 + ], + [ + -85.75831174543714, + 36.23688889591473 + ], + [ + -85.75828174537502, + 36.236891895552404 + ], + [ + -85.75809974535129, + 36.23695089586963 + ], + [ + -85.75807074536303, + 36.236953895654985 + ], + [ + -85.75798174499447, + 36.23697789546139 + ], + [ + -85.75787974480048, + 36.23699189528881 + ], + [ + -85.75773774579166, + 36.23698589594371 + ], + [ + -85.75768474543597, + 36.23698489563182 + ], + [ + -85.75751774490024, + 36.236966895630225 + ], + [ + -85.75750474545818, + 36.23695389549803 + ], + [ + -85.75747774482471, + 36.236961895547346 + ], + [ + -85.75740174566567, + 36.236953895985586 + ], + [ + -85.75704674562334, + 36.23689189564769 + ], + [ + -85.75688174447956, + 36.23684089591512 + ], + [ + -85.75670274496466, + 36.23675089517572 + ], + [ + -85.7566447454258, + 36.236716895885735 + ], + [ + -85.75652574461009, + 36.23661789532618 + ], + [ + -85.7564247451465, + 36.23649089562163 + ], + [ + -85.75631374502976, + 36.2363048958546 + ], + [ + -85.75628874521868, + 36.236276895717126 + ], + [ + -85.75626374461126, + 36.236228895576254 + ], + [ + -85.75618774433829, + 36.23613289507901 + ], + [ + -85.75592574501803, + 36.23589289506946 + ], + [ + -85.75593074453917, + 36.23585489507911 + ], + [ + -85.75592774516367, + 36.23583689514866 + ], + [ + -85.75580674453882, + 36.23567889557946 + ], + [ + -85.7555357441778, + 36.235501895602134 + ], + [ + -85.75545574439012, + 36.235490895545524 + ], + [ + -85.75513774455696, + 36.23539789559245 + ], + [ + -85.7550617447779, + 36.23538489545092 + ], + [ + -85.75488374482998, + 36.23532989580097 + ], + [ + -85.75484974455838, + 36.23530889493883 + ], + [ + -85.75480774422086, + 36.23529489567494 + ], + [ + -85.75477374422762, + 36.235274895152315 + ], + [ + -85.75473174392437, + 36.23526089496069 + ], + [ + -85.75463774401426, + 36.23517489566435 + ], + [ + -85.75460474370371, + 36.2351298953167 + ], + [ + -85.75458574390379, + 36.23507289564208 + ], + [ + -85.75456474421239, + 36.234993895680745 + ], + [ + -85.75454574409602, + 36.23491689509073 + ], + [ + -85.75454974370228, + 36.23469289540418 + ], + [ + -85.75458774435205, + 36.23458689560906 + ], + [ + -85.75460974476154, + 36.234466895196576 + ], + [ + -85.75458774370354, + 36.23425789493916 + ], + [ + -85.7545707442188, + 36.234209895349665 + ], + [ + -85.75454474411791, + 36.23416189549392 + ], + [ + -85.75447374467261, + 36.2340648955416 + ], + [ + -85.75438474386215, + 36.23397589517902 + ], + [ + -85.7541437440175, + 36.233782894918534 + ], + [ + -85.7537757443516, + 36.233562895089825 + ], + [ + -85.75328074373179, + 36.233205895339694 + ], + [ + -85.75315774371697, + 36.23312389472413 + ], + [ + -85.75303074416877, + 36.23308889467477 + ], + [ + -85.75286174312366, + 36.23310289476836 + ], + [ + -85.75267074404047, + 36.23312689465818 + ], + [ + -85.75164674377184, + 36.23327889537904 + ], + [ + -85.75136774284313, + 36.23331289521296 + ], + [ + -85.75073674262114, + 36.233329895152224 + ], + [ + -85.75011474267419, + 36.233388894946735 + ], + [ + -85.74943974221291, + 36.23338089530084 + ], + [ + -85.74894374256799, + 36.233247895315486 + ], + [ + -85.74888474216378, + 36.23322689487109 + ], + [ + -85.74880874213957, + 36.23321389528676 + ], + [ + -85.74876674286327, + 36.23319989481311 + ], + [ + -85.74873674281623, + 36.233196895320596 + ], + [ + -85.74869874207343, + 36.23318589521182 + ], + [ + -85.74858474273356, + 36.23316989555587 + ], + [ + -85.74841874236088, + 36.23313189491192 + ], + [ + -85.74829874182997, + 36.23310989563964 + ], + [ + -85.74815674278477, + 36.23307689507845 + ], + [ + -85.74806374204015, + 36.2330498950754 + ], + [ + -85.7480037421167, + 36.23303689500585 + ], + [ + -85.74791974183267, + 36.23299489562735 + ], + [ + -85.74788474225616, + 36.23298889522032 + ], + [ + -85.74767274204623, + 36.23288489534305 + ], + [ + -85.74754674254633, + 36.232809895172586 + ], + [ + -85.74731674224846, + 36.23263889500387 + ], + [ + -85.74724974167769, + 36.23258389536833 + ], + [ + -85.74703674206168, + 36.232387895044866 + ], + [ + -85.74702074236211, + 36.23236489467752 + ], + [ + -85.74694374212177, + 36.232281895340755 + ], + [ + -85.74668074169404, + 36.231959895437164 + ], + [ + -85.74666374168957, + 36.23192589525038 + ], + [ + -85.74643374125104, + 36.23165789519548 + ], + [ + -85.74640774162161, + 36.23161989535869 + ], + [ + -85.7463147411986, + 36.23151389514217 + ], + [ + -85.7462897421241, + 36.23147889486037 + ], + [ + -85.7462637412051, + 36.23145489464765 + ], + [ + -85.74624274174967, + 36.23142389519214 + ], + [ + -85.74618774145277, + 36.23136289454866 + ], + [ + -85.746068741472, + 36.23124589500146 + ], + [ + -85.74601774148272, + 36.23120089496012 + ], + [ + -85.74599274205845, + 36.23116989515938 + ], + [ + -85.74579674182651, + 36.23100589482511 + ], + [ + -85.74564874188728, + 36.23089589493432 + ], + [ + -85.74555174084362, + 36.23084089513833 + ], + [ + -85.7454567408227, + 36.23077489459476 + ], + [ + -85.74525474117104, + 36.23063589517875 + ], + [ + -85.74488174117033, + 36.23034689461533 + ], + [ + -85.74476274098623, + 36.23026489470561 + ], + [ + -85.74466074134807, + 36.23017289454517 + ], + [ + -85.74437274140435, + 36.22985289494349 + ], + [ + -85.74421974061555, + 36.22970889468173 + ], + [ + -85.74410074125171, + 36.22957889472545 + ], + [ + -85.74382874033496, + 36.2293218946252 + ], + [ + -85.7438127409643, + 36.229297894348655 + ], + [ + -85.74370974103799, + 36.22918089478354 + ], + [ + -85.74365074118556, + 36.229122894552596 + ], + [ + -85.74353174106626, + 36.22896789485346 + ], + [ + -85.74339574075572, + 36.22876289485059 + ], + [ + -85.74338774013455, + 36.228734894732696 + ], + [ + -85.7433537407895, + 36.22868689430685 + ], + [ + -85.74330174070539, + 36.228597894815714 + ], + [ + -85.74327674022531, + 36.22857089407251 + ], + [ + -85.74325974007853, + 36.228535894808076 + ], + [ + -85.74324274022382, + 36.228514894313314 + ], + [ + -85.74314974042893, + 36.22842589411466 + ], + [ + -85.7430987409569, + 36.228384894249835 + ], + [ + -85.74305674054729, + 36.22835689479153 + ], + [ + -85.7430137409416, + 36.228336894293626 + ], + [ + -85.74285274023407, + 36.22828989419222 + ], + [ + -85.74275073988179, + 36.22828289436849 + ], + [ + -85.74235373972829, + 36.22825489424531 + ], + [ + -85.7421507398205, + 36.228221894315254 + ], + [ + -85.74211073998227, + 36.2282088943278 + ], + [ + -85.7421077399079, + 36.228207894769525 + ], + [ + -85.74207773977392, + 36.22820489451546 + ], + [ + -85.7417937406353, + 36.228118894627634 + ], + [ + -85.74176074024027, + 36.22811189454026 + ], + [ + -85.7417177399791, + 36.228091894478446 + ], + [ + -85.74159973963006, + 36.22805089489043 + ], + [ + -85.7413117398728, + 36.227941894077276 + ], + [ + -85.74119174040906, + 36.22790689468908 + ], + [ + -85.74115074038738, + 36.22788689420321 + ], + [ + -85.74106574017216, + 36.22785889472052 + ], + [ + -85.74086274029122, + 36.22780389454319 + ], + [ + -85.74071773931315, + 36.22776989457905 + ], + [ + -85.74043673951194, + 36.22771389439765 + ], + [ + -85.7403457399846, + 36.22768889431993 + ], + [ + -85.74031574004985, + 36.2276848941657 + ], + [ + -85.74025573969682, + 36.22766889453311 + ], + [ + -85.74020173942655, + 36.227654894082406 + ], + [ + -85.73993773942618, + 36.22760489452725 + ], + [ + -85.73967573955126, + 36.22753089449225 + ], + [ + -85.7396427396403, + 36.22752489418843 + ], + [ + -85.73963073936257, + 36.22751689425555 + ], + [ + -85.73819273893237, + 36.22806489428787 + ], + [ + -85.73492773867322, + 36.22931189543145 + ], + [ + -85.7339927374425, + 36.229156895377656 + ], + [ + -85.73382373771535, + 36.22912889505688 + ], + [ + -85.73230673768933, + 36.22887789523629 + ], + [ + -85.73052073645637, + 36.229897895618485 + ], + [ + -85.72920173649645, + 36.230651895182966 + ], + [ + -85.72869573665228, + 36.231539895538226 + ], + [ + -85.72531673573246, + 36.23285989602609 + ], + [ + -85.72481973567426, + 36.23305489625536 + ], + [ + -85.72429473589901, + 36.23342489659058 + ], + [ + -85.72406673522708, + 36.233228896258154 + ], + [ + -85.72362973558349, + 36.233409896478896 + ], + [ + -85.72355173481397, + 36.23333789651595 + ], + [ + -85.72348173472918, + 36.233272896621145 + ], + [ + -85.72330273461142, + 36.233107896546606 + ], + [ + -85.72291673444902, + 36.23329789619883 + ], + [ + -85.72232173520749, + 36.23264889640602 + ], + [ + -85.7216807344981, + 36.23278589664107 + ], + [ + -85.72150873423772, + 36.232822896662945 + ], + [ + -85.72108273454143, + 36.23231589630794 + ], + [ + -85.71955573384007, + 36.23224389649117 + ], + [ + -85.71869373413529, + 36.23312489677324 + ], + [ + -85.71543073260506, + 36.232545896801035 + ], + [ + -85.7131617316683, + 36.232143896537536 + ], + [ + -85.7130987318316, + 36.23213289614742 + ], + [ + -85.71018373082474, + 36.23161589693944 + ], + [ + -85.70646973028781, + 36.23095689698554 + ], + [ + -85.70724372977851, + 36.228019896173926 + ], + [ + -85.70502272959982, + 36.22759789619914 + ], + [ + -85.70495372975815, + 36.22758489598155 + ], + [ + -85.70285372878418, + 36.22550889530685 + ], + [ + -85.70284472906211, + 36.225500895481964 + ], + [ + -85.70282072868555, + 36.22547689527014 + ], + [ + -85.70281272852179, + 36.225469895563315 + ], + [ + -85.70277972886524, + 36.22543689605236 + ], + [ + -85.70271972915934, + 36.225376895933536 + ], + [ + -85.70265772880846, + 36.225353895350196 + ], + [ + -85.70232672840757, + 36.22500689520692 + ], + [ + -85.70224272877945, + 36.22505989587319 + ], + [ + -85.70162172800809, + 36.225457895712964 + ], + [ + -85.70074872796225, + 36.2258738957915 + ], + [ + -85.70028372747139, + 36.226144896192224 + ], + [ + -85.7001977281775, + 36.22615889606347 + ], + [ + -85.69967872782473, + 36.22624689610551 + ], + [ + -85.69942972760076, + 36.2262648961624 + ], + [ + -85.69847872799569, + 36.22633389616124 + ], + [ + -85.69682372668814, + 36.22629789599184 + ], + [ + -85.69529672611277, + 36.226735895902635 + ], + [ + -85.6939687262281, + 36.22735989621575 + ], + [ + -85.69317572611824, + 36.22794589612844 + ], + [ + -85.6924237261988, + 36.22838889710048 + ], + [ + -85.69219472610473, + 36.228523896504306 + ], + [ + -85.69217872619235, + 36.228533896326795 + ], + [ + -85.69206272588397, + 36.22852189640234 + ], + [ + -85.69202372586095, + 36.2285258966658 + ], + [ + -85.69200672530484, + 36.2285288967709 + ], + [ + -85.6918737255676, + 36.22851789694297 + ], + [ + -85.69162372534257, + 36.22855189681603 + ], + [ + -85.69156272526705, + 36.22856389685217 + ], + [ + -85.69141072611309, + 36.22859589701408 + ], + [ + -85.69134872541032, + 36.22860089634133 + ], + [ + -85.69119172549027, + 36.22865989689293 + ], + [ + -85.69116572594443, + 36.228670896615675 + ], + [ + -85.6910897255872, + 36.22870889637381 + ], + [ + -85.69102972581084, + 36.228737896781944 + ], + [ + -85.69095672543028, + 36.228753897014826 + ], + [ + -85.69084972588409, + 36.22878589713418 + ], + [ + -85.69072872568722, + 36.22880889678725 + ], + [ + -85.6905367252892, + 36.2288628969728 + ], + [ + -85.69049172488475, + 36.22887689672863 + ], + [ + -85.69031572547838, + 36.22892289679428 + ], + [ + -85.69031472549642, + 36.228875896933836 + ], + [ + -85.690260724867, + 36.22612889659595 + ], + [ + -85.68804272480524, + 36.22619789612596 + ], + [ + -85.68783172422002, + 36.22620489610113 + ], + [ + -85.6869297238201, + 36.22559289664472 + ], + [ + -85.68596872323407, + 36.22462389659015 + ], + [ + -85.68465972354382, + 36.22491689644624 + ], + [ + -85.68328172332365, + 36.22555889638983 + ], + [ + -85.6827667230683, + 36.22510589607515 + ], + [ + -85.68170572264572, + 36.22475389609631 + ], + [ + -85.68085772196545, + 36.2244278959538 + ], + [ + -85.68081372250843, + 36.224410896037874 + ], + [ + -85.67928672217757, + 36.22463389644215 + ], + [ + -85.67842472212061, + 36.2256038970878 + ], + [ + -85.67776072097496, + 36.226045896910136 + ], + [ + -85.67703672184116, + 36.22659489701631 + ], + [ + -85.67621372122345, + 36.22714489722037 + ], + [ + -85.67530472126005, + 36.22795289722281 + ], + [ + -85.6752027209077, + 36.22804489721699 + ], + [ + -85.6750047211649, + 36.22814489774408 + ], + [ + -85.67552772120726, + 36.22351989624526 + ], + [ + -85.67554572114042, + 36.223357896057216 + ], + [ + -85.67563872037553, + 36.222534896453745 + ], + [ + -85.67426272062386, + 36.22128489583017 + ], + [ + -85.67415172065387, + 36.221183896411475 + ], + [ + -85.67329972035057, + 36.22159089640322 + ], + [ + -85.67180271952397, + 36.22042789632492 + ], + [ + -85.67114771899891, + 36.220761895688376 + ], + [ + -85.6715937192113, + 36.221251895821666 + ], + [ + -85.6719707199554, + 36.221785896036316 + ], + [ + -85.67215971948649, + 36.22245589615684 + ], + [ + -85.67202072010626, + 36.223099896444815 + ], + [ + -85.67177271918366, + 36.22362889702823 + ], + [ + -85.67020471895049, + 36.22359789632011 + ], + [ + -85.66925471851755, + 36.223579896658926 + ], + [ + -85.66918571872041, + 36.22401889653327 + ], + [ + -85.66773571841094, + 36.22390589640931 + ], + [ + -85.66758971792162, + 36.22389389675476 + ], + [ + -85.66597271831004, + 36.223794896727334 + ], + [ + -85.66597271840313, + 36.22285589661727 + ], + [ + -85.66537171720817, + 36.22282489708749 + ], + [ + -85.66375471708864, + 36.22274489677116 + ], + [ + -85.66180871679991, + 36.22242889662657 + ], + [ + -85.6608177159568, + 36.22163289682408 + ], + [ + -85.66098571601898, + 36.22063389675432 + ], + [ + -85.6594397163127, + 36.21991789661416 + ], + [ + -85.65965771569681, + 36.218588895675886 + ], + [ + -85.6586657157193, + 36.21849389652155 + ], + [ + -85.65745671494416, + 36.218379896020984 + ], + [ + -85.65645571509314, + 36.21801289621554 + ], + [ + -85.65557771496694, + 36.21748089594097 + ], + [ + -85.65525571411081, + 36.217280895745056 + ], + [ + -85.65481971426075, + 36.21701089637498 + ], + [ + -85.65440171435026, + 36.21655289555001 + ], + [ + -85.65423971363249, + 36.21637489609754 + ], + [ + -85.65414971403534, + 36.2162758956635 + ], + [ + -85.65414771416384, + 36.21627389550692 + ], + [ + -85.65410171408848, + 36.21630789618789 + ], + [ + -85.65370771416151, + 36.21657189559903 + ], + [ + -85.65282671422092, + 36.21716589632722 + ], + [ + -85.65182471294611, + 36.21753589627568 + ], + [ + -85.65188571341903, + 36.21804089607744 + ], + [ + -85.65021571336368, + 36.22060589693248 + ], + [ + -85.6497187134019, + 36.22070189695279 + ], + [ + -85.64967671312728, + 36.22070689684405 + ], + [ + -85.6449557122154, + 36.221107897471896 + ], + [ + -85.64461671139921, + 36.22091189718799 + ], + [ + -85.64327071133573, + 36.22013289703783 + ], + [ + -85.64157171093179, + 36.22110589749623 + ], + [ + -85.6414067111032, + 36.22120089784451 + ], + [ + -85.64138471076875, + 36.221368897090755 + ], + [ + -85.64125871118125, + 36.22234189727146 + ], + [ + -85.6405947104217, + 36.22222489720264 + ], + [ + -85.64024771075147, + 36.2225098973085 + ], + [ + -85.63959271062689, + 36.2230528981864 + ], + [ + -85.63916671007846, + 36.22297189789261 + ], + [ + -85.63889070974957, + 36.22269389815302 + ], + [ + -85.63860170959633, + 36.22239989803232 + ], + [ + -85.63847271033569, + 36.22195389757337 + ], + [ + -85.63814570933802, + 36.22181989732811 + ], + [ + -85.63813770986944, + 36.22182089806378 + ], + [ + -85.63760070957744, + 36.221925897547514 + ], + [ + -85.63691670900425, + 36.22231689789506 + ], + [ + -85.63646070911632, + 36.22248589797388 + ], + [ + -85.63583570922468, + 36.22241389752702 + ], + [ + -85.63549870944566, + 36.22202989770639 + ], + [ + -85.63542970864799, + 36.22159189764612 + ], + [ + -85.63546570905817, + 36.22145589762416 + ], + [ + -85.63563770939473, + 36.22082589787923 + ], + [ + -85.63657170935214, + 36.21872889712203 + ], + [ + -85.63684670862519, + 36.2181148973772 + ], + [ + -85.63695570958537, + 36.217499896963126 + ], + [ + -85.63680770902286, + 36.217062897037955 + ], + [ + -85.63609370826802, + 36.215999896221426 + ], + [ + -85.63552870905087, + 36.21549889610145 + ], + [ + -85.6346827085017, + 36.214629895967875 + ], + [ + -85.63459770783263, + 36.214543896267045 + ], + [ + -85.63470070811141, + 36.21451389663294 + ], + [ + -85.63471570836964, + 36.2145098963612 + ], + [ + -85.63482570816903, + 36.21448889588998 + ], + [ + -85.63486770862099, + 36.21447489602577 + ], + [ + -85.63499070871582, + 36.21444389675665 + ], + [ + -85.63501570785813, + 36.214442896234935 + ], + [ + -85.63508770786962, + 36.21444089586612 + ], + [ + -85.6351217083768, + 36.21442789644522 + ], + [ + -85.63512170823944, + 36.21440689587601 + ], + [ + -85.63506170793741, + 36.21433189589116 + ], + [ + -85.63505370806313, + 36.21430389649319 + ], + [ + -85.63500370843597, + 36.21424289583369 + ], + [ + -85.63498570803252, + 36.21419389627817 + ], + [ + -85.6349747081854, + 36.214183896278016 + ], + [ + -85.63489270843823, + 36.214103896350636 + ], + [ + -85.63487470787575, + 36.2140708963956 + ], + [ + -85.6348327085387, + 36.21401489646536 + ], + [ + -85.63473070775078, + 36.21390989607662 + ], + [ + -85.63467270861779, + 36.213829896480355 + ], + [ + -85.63451970781009, + 36.21367489612757 + ], + [ + -85.63437870863032, + 36.21354789632323 + ], + [ + -85.63416270839855, + 36.21335289631378 + ], + [ + -85.63293670792794, + 36.21321889646375 + ], + [ + -85.629258706564, + 36.21281689603457 + ], + [ + -85.62921070701277, + 36.21281189642413 + ], + [ + -85.62900070612123, + 36.21397789669678 + ], + [ + -85.62870470611331, + 36.21568489714332 + ], + [ + -85.62565070565557, + 36.2153058967021 + ], + [ + -85.62530270551822, + 36.21596689694668 + ], + [ + -85.62105970405287, + 36.2166288969903 + ], + [ + -85.62045770397172, + 36.21677089778119 + ], + [ + -85.61993270362835, + 36.21689489782129 + ], + [ + -85.6176327036771, + 36.21519889709056 + ], + [ + -85.61718070281168, + 36.2148648968477 + ], + [ + -85.61618070333115, + 36.21456789684635 + ], + [ + -85.61600670321394, + 36.21451689709718 + ], + [ + -85.61570470275481, + 36.21507489699939 + ], + [ + -85.61519570253299, + 36.215680897095716 + ], + [ + -85.61374470177842, + 36.21545489759426 + ], + [ + -85.61369770226823, + 36.2154478969841 + ], + [ + -85.61360270273588, + 36.2154328975443 + ], + [ + -85.61350970249516, + 36.215418897588656 + ], + [ + -85.61346970245258, + 36.215394897660836 + ], + [ + -85.61322170213471, + 36.215244897001526 + ], + [ + -85.61310970179763, + 36.21517689696065 + ], + [ + -85.613033701811, + 36.215130897763345 + ], + [ + -85.61303770222504, + 36.21517489716738 + ], + [ + -85.61311770235928, + 36.216295897828445 + ], + [ + -85.61312570179832, + 36.21632289741371 + ], + [ + -85.61313270170572, + 36.216413897695425 + ], + [ + -85.61316370217087, + 36.21659389788837 + ], + [ + -85.6132097020728, + 36.2167808973251 + ], + [ + -85.61322470253059, + 36.2168448978844 + ], + [ + -85.61334370265297, + 36.217226897740154 + ], + [ + -85.61335370181803, + 36.21725689760613 + ], + [ + -85.61337670259375, + 36.2173258976627 + ], + [ + -85.61342270221473, + 36.217554898111814 + ], + [ + -85.61343770239195, + 36.21773789763745 + ], + [ + -85.61344570265103, + 36.21794389768388 + ], + [ + -85.61344570188146, + 36.218214897975436 + ], + [ + -85.6134227028956, + 36.21861889819285 + ], + [ + -85.61339970205745, + 36.218817898210474 + ], + [ + -85.6133387029436, + 36.21953089820073 + ], + [ + -85.6133317027151, + 36.21968689832999 + ], + [ + -85.61330870260277, + 36.2198438980665 + ], + [ + -85.6132927021032, + 36.21999989820374 + ], + [ + -85.61327870244165, + 36.22009089873587 + ], + [ + -85.61324770288753, + 36.2203128986571 + ], + [ + -85.61319370231298, + 36.2206068983675 + ], + [ + -85.61309970300474, + 36.221034898719594 + ], + [ + -85.61308970278058, + 36.22107889825971 + ], + [ + -85.61305970246494, + 36.221209898306334 + ], + [ + -85.61305070278664, + 36.221254898712196 + ], + [ + -85.61080770128451, + 36.221114898819096 + ], + [ + -85.6048126995984, + 36.2207428986349 + ], + [ + -85.60433270003068, + 36.22071389900409 + ], + [ + -85.60430869943765, + 36.220963899313 + ], + [ + -85.60410269986971, + 36.22320189972427 + ], + [ + -85.60410170056095, + 36.22364489953314 + ], + [ + -85.60378969985557, + 36.22363789951173 + ], + [ + -85.60152769905235, + 36.22359389983509 + ], + [ + -85.600773699278, + 36.223579899486985 + ], + [ + -85.60042969928217, + 36.22357190000378 + ], + [ + -85.5993976990883, + 36.22354889964783 + ], + [ + -85.59905469868218, + 36.223541899932194 + ], + [ + -85.59937069876663, + 36.22397989981735 + ], + [ + -85.5994946983275, + 36.22415289994254 + ], + [ + -85.59993169867454, + 36.22477989959605 + ], + [ + -85.60008069859931, + 36.22502589974749 + ], + [ + -85.60014869881815, + 36.22517289969772 + ], + [ + -85.60020669928761, + 36.225344900056015 + ], + [ + -85.60020769874116, + 36.22535890041469 + ], + [ + -85.60023869956478, + 36.22561490002376 + ], + [ + -85.60023869935003, + 36.22589890015552 + ], + [ + -85.60023369910387, + 36.22595590010656 + ], + [ + -85.60022069886426, + 36.22612589989722 + ], + [ + -85.60021669908569, + 36.22618389991877 + ], + [ + -85.59845669811523, + 36.226092899950764 + ], + [ + -85.59470069772499, + 36.225900900097706 + ], + [ + -85.5948226977068, + 36.22520190015506 + ], + [ + -85.5940076968216, + 36.22516990049062 + ], + [ + -85.59358169675058, + 36.22515390033837 + ], + [ + -85.59350669669757, + 36.22572490008862 + ], + [ + -85.59275769707449, + 36.22584790001411 + ], + [ + -85.59253869672042, + 36.22588490059582 + ], + [ + -85.591881696652, + 36.225995900864966 + ], + [ + -85.59166369697328, + 36.226033900907595 + ], + [ + -85.5914716965981, + 36.22606690013055 + ], + [ + -85.59121869620779, + 36.2261129001982 + ], + [ + -85.59089969669027, + 36.22617990026555 + ], + [ + -85.59071069613753, + 36.22621990020685 + ], + [ + -85.59070969616018, + 36.22621990042341 + ], + [ + -85.59021069593932, + 36.22632490105103 + ], + [ + -85.589953696471, + 36.22637990084049 + ], + [ + -85.58977769664548, + 36.227636900934606 + ], + [ + -85.58970769655454, + 36.22814390134063 + ], + [ + -85.58955269600374, + 36.22925390150235 + ], + [ + -85.58948469617702, + 36.22974290172796 + ], + [ + -85.58661869585023, + 36.22959390182874 + ], + [ + -85.58549969522556, + 36.22953590136493 + ], + [ + -85.5854126950485, + 36.22959690103054 + ], + [ + -85.58533669485018, + 36.22965090157038 + ], + [ + -85.58513869461679, + 36.22975990171419 + ], + [ + -85.58504569527487, + 36.22981190153044 + ], + [ + -85.58499269472962, + 36.229840901360355 + ], + [ + -85.58487669523853, + 36.2299219011909 + ], + [ + -85.58464169483231, + 36.23011290176722 + ], + [ + -85.58428769464382, + 36.23037090188706 + ], + [ + -85.58416869499588, + 36.23043890214496 + ], + [ + -85.58410069484333, + 36.23047290135248 + ], + [ + -85.58400769464299, + 36.230512901353784 + ], + [ + -85.58397269466083, + 36.23051990145466 + ], + [ + -85.58394769464168, + 36.230532902146194 + ], + [ + -85.58367569425853, + 36.23064090146565 + ], + [ + -85.58364169407486, + 36.23066890182701 + ], + [ + -85.58349869428302, + 36.23067390186723 + ], + [ + -85.5833836949217, + 36.230662901401494 + ], + [ + -85.58324469397392, + 36.2306419017625 + ], + [ + -85.58313069431303, + 36.23062590176378 + ], + [ + -85.58290669425546, + 36.23058690186157 + ], + [ + -85.58267869475549, + 36.23055690185209 + ], + [ + -85.58223469419671, + 36.23055690192917 + ], + [ + -85.58210369448996, + 36.23055290155619 + ], + [ + -85.58163969358559, + 36.23051490188655 + ], + [ + -85.58146269340573, + 36.23051390211613 + ], + [ + -85.58129769365283, + 36.23052890180541 + ], + [ + -85.58101369345458, + 36.230543902238004 + ], + [ + -85.58094569355968, + 36.23054390158134 + ], + [ + -85.58071069407173, + 36.23052790215487 + ], + [ + -85.58063169392712, + 36.23051190231161 + ], + [ + -85.58035569352596, + 36.230442901657376 + ], + [ + -85.58031369329211, + 36.23042190212332 + ], + [ + -85.58026969405798, + 36.23040890217826 + ], + [ + -85.58013469360836, + 36.23034290169743 + ], + [ + -85.57997769345315, + 36.23028790161973 + ], + [ + -85.57994769293147, + 36.23028490164065 + ], + [ + -85.57984269381417, + 36.23025290140971 + ], + [ + -85.57969569323856, + 36.230227901726806 + ], + [ + -85.57938669342919, + 36.230200902227104 + ], + [ + -85.57898469370849, + 36.23020090230784 + ], + [ + -85.57884569271216, + 36.23021190217014 + ], + [ + -85.57871069289642, + 36.23022290230879 + ], + [ + -85.57851569286714, + 36.23024890215962 + ], + [ + -85.57848969326484, + 36.2302489015619 + ], + [ + -85.57845169257413, + 36.230254901843175 + ], + [ + -85.57809769301468, + 36.23031590237833 + ], + [ + -85.57789669246068, + 36.230360901688506 + ], + [ + -85.57784569240233, + 36.23036690231735 + ], + [ + -85.57778669320236, + 36.23038690175049 + ], + [ + -85.57765469250998, + 36.23040990209259 + ], + [ + -85.57746469267228, + 36.2304539018274 + ], + [ + -85.5771636930292, + 36.23050490197734 + ], + [ + -85.57697269261227, + 36.23053890186556 + ], + [ + -85.57681169248725, + 36.23055090202361 + ], + [ + -85.57671869246542, + 36.23056390180557 + ], + [ + -85.5763766925539, + 36.230557902060944 + ], + [ + -85.57617769286641, + 36.230531901916706 + ], + [ + -85.57611469225617, + 36.23052790199049 + ], + [ + -85.57592969178673, + 36.23050590246577 + ], + [ + -85.57556169178403, + 36.23048590221201 + ], + [ + -85.57548569257632, + 36.23048490245056 + ], + [ + -85.57540969179288, + 36.23047090235426 + ], + [ + -85.57521169171754, + 36.23047290182489 + ], + [ + -85.57511769208323, + 36.230477902024646 + ], + [ + -85.57509969218692, + 36.22984590239332 + ], + [ + -85.57495469170718, + 36.22709690128397 + ], + [ + -85.57308769082454, + 36.22709790106205 + ], + [ + -85.57241869144592, + 36.22679190191585 + ], + [ + -85.57227669057552, + 36.22639490135037 + ], + [ + -85.57230169118729, + 36.226129900934296 + ], + [ + -85.57255369125636, + 36.224747901494176 + ], + [ + -85.57280669066532, + 36.22230190046668 + ], + [ + -85.57257469054723, + 36.22200190033288 + ], + [ + -85.5724936911733, + 36.221975900531355 + ], + [ + -85.57247669068256, + 36.22197190015838 + ], + [ + -85.57232869072183, + 36.221937900735455 + ], + [ + -85.57224469050551, + 36.221917900476896 + ], + [ + -85.57216169058259, + 36.22189990083977 + ], + [ + -85.57198469089323, + 36.221858900214215 + ], + [ + -85.57144468996923, + 36.22173390014255 + ], + [ + -85.57112169081235, + 36.221658900148185 + ], + [ + -85.56930469009569, + 36.221197900231616 + ], + [ + -85.5685876893322, + 36.22103990078822 + ], + [ + -85.5682006889243, + 36.22099190056539 + ], + [ + -85.56416468830015, + 36.222475901016196 + ], + [ + -85.56407468843567, + 36.22251890130915 + ], + [ + -85.56367068807253, + 36.22266090116571 + ], + [ + -85.55753168657961, + 36.22318390125099 + ], + [ + -85.55757568618802, + 36.22511090207502 + ], + [ + -85.55958368764752, + 36.22612890190763 + ], + [ + -85.55919868695416, + 36.22709090173141 + ], + [ + -85.55838968691425, + 36.22786190232053 + ], + [ + -85.55837068663381, + 36.22832690204549 + ], + [ + -85.55829568729722, + 36.23023790296402 + ], + [ + -85.55820168684089, + 36.230461903241384 + ], + [ + -85.55796868659483, + 36.23069290304715 + ], + [ + -85.55751768727407, + 36.23090490274777 + ], + [ + -85.55688768721895, + 36.23112190312243 + ], + [ + -85.55652868646034, + 36.23118990315148 + ], + [ + -85.55587068622587, + 36.23080190293731 + ], + [ + -85.55456868584214, + 36.22977790293987 + ], + [ + -85.55314368567481, + 36.2297019029303 + ], + [ + -85.55110368499574, + 36.229594903321065 + ], + [ + -85.55110468562015, + 36.229901903348306 + ], + [ + -85.55110468552986, + 36.22996990324298 + ], + [ + -85.55110568506058, + 36.230097902886115 + ], + [ + -85.55110568538782, + 36.2301729026612 + ], + [ + -85.55110768517649, + 36.23098090302343 + ], + [ + -85.55217668603734, + 36.23106890359801 + ], + [ + -85.55500868590396, + 36.23130390343384 + ], + [ + -85.55515268692058, + 36.231315902909444 + ], + [ + -85.55537768667469, + 36.23133390337764 + ], + [ + -85.55594968675881, + 36.231382903063626 + ], + [ + -85.55593568663501, + 36.23148590311343 + ], + [ + -85.55590768665242, + 36.231710902998 + ], + [ + -85.55589068632786, + 36.23184090286198 + ], + [ + -85.55561168649525, + 36.23402190396982 + ], + [ + -85.55516768620788, + 36.2339949037405 + ], + [ + -85.55060968561956, + 36.23372190424139 + ], + [ + -85.55059668555279, + 36.23440690364318 + ], + [ + -85.54752568416049, + 36.234341904105996 + ], + [ + -85.54475268305698, + 36.23428490378267 + ], + [ + -85.54472368381848, + 36.23447790438187 + ], + [ + -85.5446996838436, + 36.23465190418739 + ], + [ + -85.54464068397024, + 36.23505990422645 + ], + [ + -85.54461368400169, + 36.235254904018454 + ], + [ + -85.54458768410788, + 36.23543090417178 + ], + [ + -85.54450968318466, + 36.23596190474555 + ], + [ + -85.544485683571, + 36.23612890456579 + ], + [ + -85.54448668388821, + 36.236139904275376 + ], + [ + -85.54448868399824, + 36.236176904707165 + ], + [ + -85.54449168388443, + 36.236227904147434 + ], + [ + -85.54451168316707, + 36.23628590446916 + ], + [ + -85.54451668317937, + 36.23630090450837 + ], + [ + -85.54452368342326, + 36.23632190438335 + ], + [ + -85.53986468216482, + 36.23602090464493 + ], + [ + -85.53947268216157, + 36.23801790476639 + ], + [ + -85.53944668276814, + 36.23815090503222 + ], + [ + -85.53866068271745, + 36.24085690539539 + ], + [ + -85.5381336815573, + 36.24080490591536 + ], + [ + -85.52620167874593, + 36.23963290566273 + ], + [ + -85.5259376784486, + 36.242391906631795 + ], + [ + -85.52593767914198, + 36.242392907001644 + ], + [ + -85.52593767816948, + 36.24239790703119 + ], + [ + -85.52603467888477, + 36.242422906555596 + ], + [ + -85.52620267853854, + 36.24244990671091 + ], + [ + -85.52693467908907, + 36.24254490626369 + ], + [ + -85.52717167923564, + 36.24256790699481 + ], + [ + -85.52735767923245, + 36.242567906594694 + ], + [ + -85.52738467907507, + 36.24256690640683 + ], + [ + -85.52759067870218, + 36.2425519062787 + ], + [ + -85.52835367887413, + 36.24244490644724 + ], + [ + -85.5285216794886, + 36.242433906555185 + ], + [ + -85.52885767912721, + 36.24244790665925 + ], + [ + -85.52963568011435, + 36.242515906097196 + ], + [ + -85.53011567971494, + 36.2425709060037 + ], + [ + -85.53114667978086, + 36.24268690657207 + ], + [ + -85.53163568023894, + 36.24273090608941 + ], + [ + -85.53177168043926, + 36.24274290681923 + ], + [ + -85.53215367994748, + 36.24278490603726 + ], + [ + -85.53246668090036, + 36.24278490644122 + ], + [ + -85.5325726808957, + 36.242777906670774 + ], + [ + -85.53283268098667, + 36.242746905985975 + ], + [ + -85.53294668100384, + 36.242750906608656 + ], + [ + -85.53306268056507, + 36.242777906162274 + ], + [ + -85.53318668086018, + 36.242728905945924 + ], + [ + -85.53332968059952, + 36.242674905961394 + ], + [ + -85.53357168042848, + 36.24261490643599 + ], + [ + -85.533701681079, + 36.24258390661773 + ], + [ + -85.53296168152075, + 36.24794490784317 + ], + [ + -85.5301556799731, + 36.2476919070298 + ], + [ + -85.52987868062218, + 36.25043090770971 + ], + [ + -85.53484468217168, + 36.25093390827007 + ], + [ + -85.53475868189332, + 36.25232690849301 + ], + [ + -85.53475668231404, + 36.252359908293236 + ], + [ + -85.53450568255377, + 36.256386909484995 + ], + [ + -85.53402068144787, + 36.25636690926728 + ], + [ + -85.53391968241382, + 36.25874990996649 + ], + [ + -85.5307856815144, + 36.258753909889094 + ], + [ + -85.53056668128002, + 36.261218910476224 + ], + [ + -85.52347267942925, + 36.2606239099263 + ], + [ + -85.52337367926602, + 36.260614910072995 + ], + [ + -85.5198616776526, + 36.260530910582645 + ], + [ + -85.51986567815725, + 36.26049491073755 + ], + [ + -85.51987667759292, + 36.26034291020851 + ], + [ + -85.51988767760353, + 36.260191910036184 + ], + [ + -85.51997567823705, + 36.25902491063414 + ], + [ + -85.52009667785191, + 36.258301910028976 + ], + [ + -85.52012767767687, + 36.25810191011054 + ], + [ + -85.52022167841997, + 36.257520910297586 + ], + [ + -85.51257567519981, + 36.256885909962975 + ], + [ + -85.51202967590307, + 36.258912910816534 + ], + [ + -85.51197567531207, + 36.25941091042743 + ], + [ + -85.51170767582627, + 36.26178991100305 + ], + [ + -85.51169367528679, + 36.261998911188655 + ], + [ + -85.51162167576686, + 36.2630669112516 + ], + [ + -85.51151467620161, + 36.2645489118804 + ], + [ + -85.50941767563891, + 36.264562912023194 + ], + [ + -85.50947967592964, + 36.265760911551375 + ], + [ + -85.50947067499055, + 36.265792911806166 + ], + [ + -85.5092936755902, + 36.26654391224258 + ], + [ + -85.50977967565365, + 36.26662891184941 + ], + [ + -85.50958167609531, + 36.26853591232283 + ], + [ + -85.50950767519232, + 36.26924991241307 + ], + [ + -85.50949767579628, + 36.269343912882285 + ], + [ + -85.50921667546521, + 36.27120591328576 + ], + [ + -85.50921267609374, + 36.271220913251774 + ], + [ + -85.50828767602799, + 36.27772691448278 + ], + [ + -85.50736767527376, + 36.277642914406684 + ], + [ + -85.50549467501696, + 36.27811191485255 + ], + [ + -85.50551267532774, + 36.27876491460482 + ], + [ + -85.49995467394096, + 36.2792099153197 + ], + [ + -85.49987667371619, + 36.279219915218185 + ], + [ + -85.49851567324322, + 36.27935891534859 + ], + [ + -85.49829267290278, + 36.279381915613094 + ], + [ + -85.49400467164905, + 36.279823915654724 + ], + [ + -85.4939006724304, + 36.281343915393535 + ], + [ + -85.49724067337657, + 36.28248491603638 + ], + [ + -85.49728367247748, + 36.28249991547211 + ], + [ + -85.4972906726418, + 36.28251591604502 + ], + [ + -85.49730667294577, + 36.282586915983934 + ], + [ + -85.49731167263722, + 36.28260891578289 + ], + [ + -85.49737467299684, + 36.282791915877326 + ], + [ + -85.49737867296614, + 36.28284991624017 + ], + [ + -85.49738667344393, + 36.28294091616254 + ], + [ + -85.497390672972, + 36.282990916372036 + ], + [ + -85.49736067348067, + 36.283117916213236 + ], + [ + -85.49727067272973, + 36.28331391641382 + ], + [ + -85.49725367271363, + 36.28340291618297 + ], + [ + -85.49724867253023, + 36.28350291570368 + ], + [ + -85.49717967354506, + 36.28379891622637 + ], + [ + -85.49715367331827, + 36.28386691611069 + ], + [ + -85.49710367305411, + 36.28393591653504 + ], + [ + -85.49706067308945, + 36.28397091592755 + ], + [ + -85.49694167325677, + 36.284024915832966 + ], + [ + -85.49687067282223, + 36.284044915849385 + ], + [ + -85.4967966731024, + 36.28406691576691 + ], + [ + -85.49661467272446, + 36.28411091627257 + ], + [ + -85.49628267241734, + 36.28416591639552 + ], + [ + -85.49618867228816, + 36.28417291647401 + ], + [ + -85.49600667275404, + 36.28416191630359 + ], + [ + -85.49591367256436, + 36.28412791654641 + ], + [ + -85.49583767311415, + 36.284085916281406 + ], + [ + -85.49579467268248, + 36.28405091641979 + ], + [ + -85.49575267224739, + 36.28400291608214 + ], + [ + -85.49572767286186, + 36.283933916085175 + ], + [ + -85.49566867204258, + 36.283858916283656 + ], + [ + -85.49561767236486, + 36.28375491607065 + ], + [ + -85.49554267257717, + 36.2835689162261 + ], + [ + -85.49545767280121, + 36.28347591584756 + ], + [ + -85.49528867286469, + 36.283334916192416 + ], + [ + -85.49520367222051, + 36.2832879158017 + ], + [ + -85.49511867274268, + 36.28325891636556 + ], + [ + -85.49505967289556, + 36.2832599163131 + ], + [ + -85.4949236724369, + 36.28327891636473 + ], + [ + -85.49474467212544, + 36.28336891596185 + ], + [ + -85.49466067270318, + 36.28342991642338 + ], + [ + -85.49457467265866, + 36.28351991609582 + ], + [ + -85.49451467253678, + 36.283656915917135 + ], + [ + -85.4944506718125, + 36.28383191612102 + ], + [ + -85.49442067261096, + 36.28394691635307 + ], + [ + -85.49438567257377, + 36.28422091626262 + ], + [ + -85.4943306718577, + 36.28458291609122 + ], + [ + -85.49432467170845, + 36.2846269165766 + ], + [ + -85.49432167224134, + 36.28535591666438 + ], + [ + -85.49428267238487, + 36.28565491670592 + ], + [ + -85.49426467191898, + 36.2857579168037 + ], + [ + -85.49427667217766, + 36.28588691647754 + ], + [ + -85.49429367190447, + 36.286057916853714 + ], + [ + -85.49432167240506, + 36.286143916780794 + ], + [ + -85.49437667283472, + 36.28640191687273 + ], + [ + -85.49438067215006, + 36.28648691696462 + ], + [ + -85.4943576719937, + 36.28674591645625 + ], + [ + -85.49431567261594, + 36.28687591676301 + ], + [ + -85.49428867225538, + 36.286923917305685 + ], + [ + -85.49424667185639, + 36.28697591688956 + ], + [ + -85.49418667240143, + 36.287027916849304 + ], + [ + -85.49414567187569, + 36.287050916581514 + ], + [ + -85.49411967241004, + 36.28706591675325 + ], + [ + -85.49404267206266, + 36.28710191731204 + ], + [ + -85.4939916724588, + 36.28711591700289 + ], + [ + -85.49390767203498, + 36.28715091665762 + ], + [ + -85.49377067266506, + 36.287184916567966 + ], + [ + -85.49354967219008, + 36.28725291717332 + ], + [ + -85.49343167235557, + 36.287294917129444 + ], + [ + -85.49316267162729, + 36.28736591701046 + ], + [ + -85.49313367208067, + 36.287369916704 + ], + [ + -85.49298867237918, + 36.28741091711522 + ], + [ + -85.49285767167137, + 36.28743491686889 + ], + [ + -85.49271367196272, + 36.287441917173275 + ], + [ + -85.49245367196276, + 36.287437916680055 + ], + [ + -85.49215767211554, + 36.287402917400065 + ], + [ + -85.49212267170029, + 36.28739591747173 + ], + [ + -85.49205567127359, + 36.287374916889036 + ], + [ + -85.49188567223469, + 36.287298917143126 + ], + [ + -85.49172867167867, + 36.28721691740512 + ], + [ + -85.49164067206264, + 36.287147916672346 + ], + [ + -85.49150467124346, + 36.287023917012654 + ], + [ + -85.49147967115823, + 36.28699291708232 + ], + [ + -85.49137767137715, + 36.28688891690706 + ], + [ + -85.49119167155652, + 36.28671991670003 + ], + [ + -85.4910316709008, + 36.28651691713752 + ], + [ + -85.49098967149388, + 36.28647991728996 + ], + [ + -85.49092967154128, + 36.2864449171369 + ], + [ + -85.49088767187996, + 36.28643291713468 + ], + [ + -85.49081967108428, + 36.28643091660081 + ], + [ + -85.49079967088244, + 36.28643591704668 + ], + [ + -85.4907686713313, + 36.286444916737246 + ], + [ + -85.49063267137416, + 36.286540916838014 + ], + [ + -85.49059867084911, + 36.2865819170271 + ], + [ + -85.4904456711114, + 36.28680791675728 + ], + [ + -85.49038867150036, + 36.28694991714408 + ], + [ + -85.49035467113926, + 36.287258917038756 + ], + [ + -85.49033667088325, + 36.28752491679765 + ], + [ + -85.49181967163344, + 36.288343917300296 + ], + [ + -85.49219167141763, + 36.28885991718392 + ], + [ + -85.49224567188587, + 36.288933917035585 + ], + [ + -85.49260267166059, + 36.28953291712844 + ], + [ + -85.49316967233558, + 36.29003491781184 + ], + [ + -85.49361667186938, + 36.290188917927075 + ], + [ + -85.4942736727698, + 36.29031891778781 + ], + [ + -85.49473167230084, + 36.29059391792897 + ], + [ + -85.49470167296235, + 36.29237991840029 + ], + [ + -85.49469067323649, + 36.29304191820608 + ], + [ + -85.49466867304484, + 36.2943579181894 + ], + [ + -85.49463167257232, + 36.29668791869271 + ], + [ + -85.49463167287088, + 36.296688919046424 + ], + [ + -85.49463167317516, + 36.29668991849892 + ], + [ + -85.49463167347372, + 36.29669091885266 + ], + [ + -85.49463167265894, + 36.29669191920165 + ], + [ + -85.49463167296324, + 36.296692918654166 + ], + [ + -85.49465067361454, + 36.29669191841697 + ], + [ + -85.49651867334818, + 36.29643291896682 + ], + [ + -85.4965806732901, + 36.29642491838228 + ], + [ + -85.4967686731912, + 36.29640291825833 + ], + [ + -85.49682567385238, + 36.29639691907328 + ], + [ + -85.49683167427364, + 36.29639591845991 + ], + [ + -85.49678967331936, + 36.2973049187907 + ], + [ + -85.49666667362101, + 36.30003391967818 + ], + [ + -85.49662667435494, + 36.3009439195196 + ], + [ + -85.4967356745194, + 36.30112991946035 + ], + [ + -85.49710267411983, + 36.30162392014987 + ], + [ + -85.49714467404308, + 36.30165992001098 + ], + [ + -85.49741067470043, + 36.30189092013623 + ], + [ + -85.49760967431574, + 36.302036919685335 + ], + [ + -85.49773767431553, + 36.30235192024662 + ], + [ + -85.49618267343145, + 36.30231191999435 + ], + [ + -85.49601467403018, + 36.3023069201302 + ], + [ + -85.49512867393021, + 36.302284920036044 + ], + [ + -85.49297867340256, + 36.302283919765436 + ], + [ + -85.49290167272343, + 36.30314792056485 + ], + [ + -85.49290267353226, + 36.30335491981531 + ], + [ + -85.49220967259735, + 36.303339920657386 + ], + [ + -85.49076367249597, + 36.3033159202981 + ], + [ + -85.49070767178702, + 36.30152291953959 + ], + [ + -85.4907606726497, + 36.30127291979652 + ], + [ + -85.49065267257045, + 36.30115292018925 + ], + [ + -85.49047667186885, + 36.30089691959669 + ], + [ + -85.49029467243781, + 36.30038791985844 + ], + [ + -85.49019367233547, + 36.30010191978115 + ], + [ + -85.49002967199668, + 36.29963992000767 + ], + [ + -85.48999767223332, + 36.29945291930896 + ], + [ + -85.4869836709828, + 36.299972920240904 + ], + [ + -85.48423567014127, + 36.300530920249464 + ], + [ + -85.48421667101351, + 36.302841920559686 + ], + [ + -85.48347767062431, + 36.3036999211485 + ], + [ + -85.47993966907924, + 36.30343792087746 + ], + [ + -85.47985166885285, + 36.30343192054439 + ], + [ + -85.47990166972828, + 36.303381920709874 + ], + [ + -85.47998066956886, + 36.30329592118474 + ], + [ + -85.4800226691617, + 36.30323492087371 + ], + [ + -85.48003566980594, + 36.30320092068162 + ], + [ + -85.48005066925259, + 36.30313092094468 + ], + [ + -85.48004566931648, + 36.303095920494485 + ], + [ + -85.48000766911989, + 36.303031921144886 + ], + [ + -85.47989066913586, + 36.30292492105055 + ], + [ + -85.47981966928437, + 36.302882920789706 + ], + [ + -85.47970866883564, + 36.30282592063351 + ], + [ + -85.479668669182, + 36.30280992042011 + ], + [ + -85.47949466952696, + 36.302784920628504 + ], + [ + -85.47939066911435, + 36.30278692057592 + ], + [ + -85.47925866891205, + 36.302778920699325 + ], + [ + -85.47921566949725, + 36.30276992069007 + ], + [ + -85.47912766884568, + 36.30276292037334 + ], + [ + -85.47899466889663, + 36.302765920668186 + ], + [ + -85.47877466875595, + 36.302767920355215 + ], + [ + -85.47868666853445, + 36.30276492112491 + ], + [ + -85.47850866860249, + 36.30277192112064 + ], + [ + -85.47846566869661, + 36.302779920508186 + ], + [ + -85.47824366828952, + 36.30279992053218 + ], + [ + -85.4781566685368, + 36.3028159210965 + ], + [ + -85.47799066832356, + 36.302867920344205 + ], + [ + -85.47791066873468, + 36.302899920528056 + ], + [ + -85.47782966871303, + 36.30292592053833 + ], + [ + -85.47772166847787, + 36.302988920510415 + ], + [ + -85.4776666685601, + 36.3030409210917 + ], + [ + -85.47750366819267, + 36.303158920674 + ], + [ + -85.47743066885909, + 36.303199920607334 + ], + [ + -85.4773916687114, + 36.303216921136865 + ], + [ + -85.47730766842432, + 36.30323992119437 + ], + [ + -85.4772196687866, + 36.30324992096312 + ], + [ + -85.47712966824365, + 36.30324492116394 + ], + [ + -85.47699866797662, + 36.30323092103939 + ], + [ + -85.47678766861684, + 36.303191920914145 + ], + [ + -85.47670566836618, + 36.30316192049757 + ], + [ + -85.4765036685964, + 36.30310192080236 + ], + [ + -85.47641766816803, + 36.30308892055996 + ], + [ + -85.47628966786561, + 36.30305992107526 + ], + [ + -85.47621166812864, + 36.30302592092221 + ], + [ + -85.47617066838983, + 36.30301392112962 + ], + [ + -85.47609766857984, + 36.302972921177606 + ], + [ + -85.47602966769905, + 36.30292692125961 + ], + [ + -85.47585366771396, + 36.3027209205252 + ], + [ + -85.47579666819155, + 36.302666921189314 + ], + [ + -85.47573166817807, + 36.30261792043586 + ], + [ + -85.47560666781214, + 36.30259792107156 + ], + [ + -85.47543066838595, + 36.30257992092664 + ], + [ + -85.47520866796216, + 36.30256392074401 + ], + [ + -85.47507866748917, + 36.30254292040757 + ], + [ + -85.47485666783521, + 36.30251692056994 + ], + [ + -85.47467966800004, + 36.3025029208978 + ], + [ + -85.47459166719368, + 36.30250192118834 + ], + [ + -85.47441366742747, + 36.302483920781484 + ], + [ + -85.47432766782042, + 36.302466921246996 + ], + [ + -85.4741496680611, + 36.30244592119785 + ], + [ + -85.4740166679755, + 36.3024379211576 + ], + [ + -85.4739286674102, + 36.30244092057099 + ], + [ + -85.47375066728972, + 36.302435921054844 + ], + [ + -85.47357066711324, + 36.302418920963596 + ], + [ + -85.47339366679911, + 36.302410920614264 + ], + [ + -85.47330466713528, + 36.302401920913354 + ], + [ + -85.47313266757995, + 36.30237092125577 + ], + [ + -85.47286466712981, + 36.302355921234806 + ], + [ + -85.47273166714703, + 36.30234092092172 + ], + [ + -85.47268666697424, + 36.30233892133542 + ], + [ + -85.4725566675013, + 36.30231692063748 + ], + [ + -85.47234866737041, + 36.30225892093387 + ], + [ + -85.472049666388, + 36.30219292087046 + ], + [ + -85.47189766710324, + 36.30212192060286 + ], + [ + -85.47177866709345, + 36.30207492045568 + ], + [ + -85.47156766652483, + 36.302023920470795 + ], + [ + -85.47139366660039, + 36.30199592055614 + ], + [ + -85.47130466650462, + 36.30198692117934 + ], + [ + -85.47121566648782, + 36.3019879207521 + ], + [ + -85.47108366623775, + 36.30200292093842 + ], + [ + -85.47099566605796, + 36.302019921344446 + ], + [ + -85.47087066673839, + 36.302054921119804 + ], + [ + -85.47079166600267, + 36.30208792133376 + ], + [ + -85.47071466708147, + 36.302124920852904 + ], + [ + -85.47061766700244, + 36.3021969207761 + ], + [ + -85.4705236662184, + 36.30227292074371 + ], + [ + -85.47043766609073, + 36.3023539213907 + ], + [ + -85.47033066692318, + 36.302467920908505 + ], + [ + -85.47027366616307, + 36.30252092145742 + ], + [ + -85.47020066662246, + 36.30260992132349 + ], + [ + -85.47011066610682, + 36.302731921335905 + ], + [ + -85.47003366647445, + 36.30285892144405 + ], + [ + -85.4699996669306, + 36.30292392145551 + ], + [ + -85.46993366692543, + 36.30309292140096 + ], + [ + -85.46991166677424, + 36.30312392147228 + ], + [ + -85.46974466583225, + 36.30344992120561 + ], + [ + -85.46963566611191, + 36.303604921219666 + ], + [ + -85.46960766668452, + 36.30363192144747 + ], + [ + -85.46950166594159, + 36.30378992112504 + ], + [ + -85.46943166609533, + 36.30388092174796 + ], + [ + -85.46926766644644, + 36.30404792129659 + ], + [ + -85.4688996660465, + 36.30434992112004 + ], + [ + -85.46876066609978, + 36.304437921800805 + ], + [ + -85.46868666570366, + 36.304475921825535 + ], + [ + -85.4686536664445, + 36.30449992133355 + ], + [ + -85.4685806657122, + 36.30454092133323 + ], + [ + -85.468390666538, + 36.30463492123731 + ], + [ + -85.46803666550856, + 36.304852921602134 + ], + [ + -85.46795966601152, + 36.30488792135087 + ], + [ + -85.46792366576224, + 36.304908921719836 + ], + [ + -85.46780266625811, + 36.30495392140844 + ], + [ + -85.46767666538591, + 36.304990921380565 + ], + [ + -85.46763366533605, + 36.30499992177723 + ], + [ + -85.46755466595036, + 36.30503092187176 + ], + [ + -85.46747066525072, + 36.30505492176126 + ], + [ + -85.46738466614438, + 36.305072921496084 + ], + [ + -85.46734466543411, + 36.30508592198792 + ], + [ + -85.46712866580899, + 36.30513292132952 + ], + [ + -85.46699866573093, + 36.30515192188094 + ], + [ + -85.46682066515915, + 36.30515292150761 + ], + [ + -85.46669066588676, + 36.3051329215472 + ], + [ + -85.46660666518058, + 36.30510792164481 + ], + [ + -85.46653566590821, + 36.30506592170342 + ], + [ + -85.46651166522513, + 36.30503592152561 + ], + [ + -85.46648566578838, + 36.30496792170008 + ], + [ + -85.46647966572819, + 36.30471892126478 + ], + [ + -85.46650866578278, + 36.30464292157185 + ], + [ + -85.46652566493783, + 36.304572921499364 + ], + [ + -85.4665386660337, + 36.30453892184723 + ], + [ + -85.46668166501917, + 36.30431692127413 + ], + [ + -85.46690666531296, + 36.304008921097456 + ], + [ + -85.46699066552812, + 36.30388292176768 + ], + [ + -85.46714166583203, + 36.303624921594135 + ], + [ + -85.46722066572144, + 36.30345692132742 + ], + [ + -85.4672706651393, + 36.303319921296215 + ], + [ + -85.46734766583684, + 36.3031529214888 + ], + [ + -85.46738466543383, + 36.30305092150941 + ], + [ + -85.46741466509035, + 36.30294592116912 + ], + [ + -85.46745466593913, + 36.30277192165277 + ], + [ + -85.46746266615686, + 36.30262992131476 + ], + [ + -85.46746066580162, + 36.30248792108292 + ], + [ + -85.46743866546939, + 36.302346921424686 + ], + [ + -85.46736266503471, + 36.3021419208885 + ], + [ + -85.46730066596406, + 36.30193492089195 + ], + [ + -85.46726166510969, + 36.30183292129751 + ], + [ + -85.46721266595517, + 36.301734921395735 + ], + [ + -85.46717566544363, + 36.30167092144214 + ], + [ + -85.46710966593821, + 36.30157892078511 + ], + [ + -85.46705766573162, + 36.30152192113426 + ], + [ + -85.46697166508146, + 36.30144192117679 + ], + [ + -85.4668776657665, + 36.30136792134681 + ], + [ + -85.46676866543072, + 36.30130592079709 + ], + [ + -85.4666526651192, + 36.30124992112442 + ], + [ + -85.46657366571417, + 36.301218921270255 + ], + [ + -85.46649066562044, + 36.30119492059828 + ], + [ + -85.46645066497064, + 36.301178921391546 + ], + [ + -85.4663676648394, + 36.301156921339526 + ], + [ + -85.46628166508275, + 36.301138921115275 + ], + [ + -85.46614766483161, + 36.30112992115339 + ], + [ + -85.46601666501302, + 36.301141920971645 + ], + [ + -85.46588566478019, + 36.30115992095966 + ], + [ + -85.46563266503327, + 36.3012249209504 + ], + [ + -85.46551766486402, + 36.30127792068403 + ], + [ + -85.46543566464833, + 36.30130592131565 + ], + [ + -85.46536266518171, + 36.30134692121546 + ], + [ + -85.46522566493613, + 36.30143692114746 + ], + [ + -85.46509766530131, + 36.30153792136266 + ], + [ + -85.46503966514716, + 36.30159192081029 + ], + [ + -85.46495766468372, + 36.30167692138397 + ], + [ + -85.46485566527072, + 36.30179292091684 + ], + [ + -85.46475966427336, + 36.30191192093586 + ], + [ + -85.46472766482256, + 36.30197792149798 + ], + [ + -85.4646876650507, + 36.302079920946404 + ], + [ + -85.46466266505392, + 36.30222192158138 + ], + [ + -85.46466066434624, + 36.30229292151906 + ], + [ + -85.46467166484909, + 36.30236292098907 + ], + [ + -85.46474866446016, + 36.30260492086255 + ], + [ + -85.46476666502842, + 36.30267492156479 + ], + [ + -85.46479866474081, + 36.30274092153876 + ], + [ + -85.46484166444404, + 36.302916921510864 + ], + [ + -85.46484066434829, + 36.3030239214177 + ], + [ + -85.46483066478812, + 36.30320192180591 + ], + [ + -85.46481966459645, + 36.3032719211919 + ], + [ + -85.46479066521067, + 36.30333892185786 + ], + [ + -85.46476866519926, + 36.303369921902075 + ], + [ + -85.46471166539189, + 36.30342592179473 + ], + [ + -85.46467866535976, + 36.30344992112686 + ], + [ + -85.46456766459484, + 36.303509921182766 + ], + [ + -85.4645076650046, + 36.30353792127023 + ], + [ + -85.46448966500661, + 36.30354492151835 + ], + [ + -85.4644476649776, + 36.303556921763025 + ], + [ + -85.46432866482427, + 36.303606921399535 + ], + [ + -85.4640496644929, + 36.30369692162271 + ], + [ + -85.46378766484185, + 36.303742921631915 + ], + [ + -85.46339266482623, + 36.30379092188665 + ], + [ + -85.46304266427181, + 36.3038269218141 + ], + [ + -85.46273866454574, + 36.30388192184441 + ], + [ + -85.46247766387891, + 36.30392292169756 + ], + [ + -85.4620396636776, + 36.30398492145223 + ], + [ + -85.46173366432926, + 36.30401792214532 + ], + [ + -85.4616466643272, + 36.304023921743095 + ], + [ + -85.46160366433583, + 36.30403092199869 + ], + [ + -85.46151466448617, + 36.30403792196071 + ], + [ + -85.46134066406758, + 36.30406192139605 + ], + [ + -85.4612536640981, + 36.30406692216189 + ], + [ + -85.46112166387287, + 36.30408292175198 + ], + [ + -85.46094866369542, + 36.304110921983636 + ], + [ + -85.46077266373632, + 36.304130921995906 + ], + [ + -85.460597663755, + 36.30415792146755 + ], + [ + -85.46015766371174, + 36.3041999218824 + ], + [ + -85.45997966332331, + 36.30420592220245 + ], + [ + -85.45957966294145, + 36.30420492180094 + ], + [ + -85.45949066313463, + 36.304208921922154 + ], + [ + -85.4592696631478, + 36.3042089215194 + ], + [ + -85.45909166353862, + 36.304193922154674 + ], + [ + -85.45896766331666, + 36.30415292159671 + ], + [ + -85.45889166318439, + 36.30411692210222 + ], + [ + -85.45885566274636, + 36.304095922118975 + ], + [ + -85.45879166307172, + 36.30404592177171 + ], + [ + -85.45873366303832, + 36.30399292195684 + ], + [ + -85.45869366369013, + 36.303932921830395 + ], + [ + -85.4586396634312, + 36.303834922222656 + ], + [ + -85.45859866304421, + 36.30369692164642 + ], + [ + -85.45859466330958, + 36.30362692143501 + ], + [ + -85.45858766309696, + 36.30359192191924 + ], + [ + -85.45858566305371, + 36.30348592158071 + ], + [ + -85.45857166362207, + 36.303380921932046 + ], + [ + -85.45856466311874, + 36.30323992200776 + ], + [ + -85.45857666278529, + 36.303099921896184 + ], + [ + -85.45861166298805, + 36.30296092148025 + ], + [ + -85.4586576633353, + 36.30282292198655 + ], + [ + -85.45869166325974, + 36.30275792151824 + ], + [ + -85.45870966288794, + 36.30268992135046 + ], + [ + -85.45873566322979, + 36.30262292129912 + ], + [ + -85.45876766333168, + 36.302557921766926 + ], + [ + -85.45886066336779, + 36.30243992151831 + ], + [ + -85.45903466338957, + 36.30227992184931 + ], + [ + -85.45914166348133, + 36.30221592105041 + ], + [ + -85.45925566295449, + 36.302161921642245 + ], + [ + -85.45938366322316, + 36.302130921644924 + ], + [ + -85.45956266299919, + 36.30212692108298 + ], + [ + -85.45974566371706, + 36.3021589210898 + ], + [ + -85.45995666289132, + 36.302216921667224 + ], + [ + -85.46030466302594, + 36.30237092115459 + ], + [ + -85.4606386631633, + 36.30254492190336 + ], + [ + -85.46111166409472, + 36.302809921804965 + ], + [ + -85.461374664049, + 36.302943921569884 + ], + [ + -85.46141466369325, + 36.30295792173858 + ], + [ + -85.46145766345661, + 36.302966921779316 + ], + [ + -85.46161566384963, + 36.30303092159147 + ], + [ + -85.46169766346769, + 36.30305492114013 + ], + [ + -85.4617346641304, + 36.30307492167967 + ], + [ + -85.46185666396437, + 36.30310992191621 + ], + [ + -85.46202966377479, + 36.30313292122783 + ], + [ + -85.46206866367692, + 36.30314792158854 + ], + [ + -85.46215566384312, + 36.3031619215255 + ], + [ + -85.46219366383734, + 36.30317792135473 + ], + [ + -85.4622796643212, + 36.303190921810604 + ], + [ + -85.46241266446827, + 36.30319992159339 + ], + [ + -85.46249966395449, + 36.303210921123664 + ], + [ + -85.46258666438926, + 36.30321292193184 + ], + [ + -85.462765664698, + 36.303203921169235 + ], + [ + -85.46285066452025, + 36.30318192156907 + ], + [ + -85.46295666481107, + 36.30312092124496 + ], + [ + -85.4629906639606, + 36.30309692140883 + ], + [ + -85.46308166476138, + 36.3030189213656 + ], + [ + -85.46315466481491, + 36.30293092162045 + ], + [ + -85.46321066482123, + 36.30283592139612 + ], + [ + -85.46323866440993, + 36.3027689211611 + ], + [ + -85.46327066457933, + 36.30266492113597 + ], + [ + -85.46329466484126, + 36.30249092130508 + ], + [ + -85.46331666416081, + 36.302246921253506 + ], + [ + -85.46331166387448, + 36.30221192096886 + ], + [ + -85.46331466431, + 36.302142921682815 + ], + [ + -85.46330466462327, + 36.30200492147655 + ], + [ + -85.46328766395727, + 36.30183192110461 + ], + [ + -85.46329466483047, + 36.30179792126822 + ], + [ + -85.46327366391324, + 36.301588921071954 + ], + [ + -85.46325466396418, + 36.30148392136098 + ], + [ + -85.46324266381357, + 36.30144992098411 + ], + [ + -85.46323366443691, + 36.301343921148934 + ], + [ + -85.4632326647218, + 36.30127292140637 + ], + [ + -85.4631636644366, + 36.30085292108841 + ], + [ + -85.46312866451923, + 36.30067892056525 + ], + [ + -85.46312566402699, + 36.30064392102398 + ], + [ + -85.4630946646565, + 36.30050392050494 + ], + [ + -85.46305366433222, + 36.300223920755975 + ], + [ + -85.46296066377352, + 36.29998692057964 + ], + [ + -85.46285866360967, + 36.29979192039976 + ], + [ + -85.46282066427639, + 36.29972892037848 + ], + [ + -85.46279366367254, + 36.29970092119695 + ], + [ + -85.46275166414179, + 36.29963792050102 + ], + [ + -85.46262166444386, + 36.299492920586204 + ], + [ + -85.46244666390177, + 36.29933292062888 + ], + [ + -85.46238166358489, + 36.299284921062586 + ], + [ + -85.46211266402746, + 36.29910192073389 + ], + [ + -85.46208266419451, + 36.2990769208191 + ], + [ + -85.4619586634439, + 36.299008920897144 + ], + [ + -85.46189766338138, + 36.29897592073655 + ], + [ + -85.46165966422205, + 36.29887592086638 + ], + [ + -85.46153466419526, + 36.298837920352476 + ], + [ + -85.46149066341823, + 36.29882992071881 + ], + [ + -85.46141166309728, + 36.2987969203425 + ], + [ + -85.46108166352874, + 36.2986899206362 + ], + [ + -85.46099766298823, + 36.29866992032045 + ], + [ + -85.46083466308347, + 36.29861292048205 + ], + [ + -85.4606656639117, + 36.29857092059512 + ], + [ + -85.46058466325155, + 36.29853992046944 + ], + [ + -85.4604156631212, + 36.29849892058031 + ], + [ + -85.46026866315101, + 36.298446921050136 + ], + [ + -85.4600116629193, + 36.29835492062253 + ], + [ + -85.45960566361117, + 36.298225921082235 + ], + [ + -85.45936666259774, + 36.29814492070145 + ], + [ + -85.45925266331881, + 36.298107920491496 + ], + [ + -85.45929566277685, + 36.29806092043009 + ], + [ + -85.45958366356622, + 36.29773392075936 + ], + [ + -85.45999966288637, + 36.29725592047251 + ], + [ + -85.46054166289498, + 36.29678392075566 + ], + [ + -85.46110266391089, + 36.29633091987781 + ], + [ + -85.4616636636265, + 36.29590092000379 + ], + [ + -85.46220566356936, + 36.295631919807754 + ], + [ + -85.46257666412217, + 36.29541391964923 + ], + [ + -85.46296166358373, + 36.295171919749784 + ], + [ + -85.46319966394547, + 36.29488191948885 + ], + [ + -85.46322266398626, + 36.29485291939878 + ], + [ + -85.46328566415487, + 36.29474591961606 + ], + [ + -85.46337466421481, + 36.294587919780206 + ], + [ + -85.46347966342358, + 36.29420791930127 + ], + [ + -85.4634886644377, + 36.29389291952938 + ], + [ + -85.46348366369355, + 36.29368691972655 + ], + [ + -85.46347966362386, + 36.29348291965413 + ], + [ + -85.46049666268813, + 36.291655919628006 + ], + [ + -85.46007166286086, + 36.29142691890656 + ], + [ + -85.4599026627741, + 36.291335919267745 + ], + [ + -85.45891266270671, + 36.29067191958753 + ], + [ + -85.458858661933, + 36.290642918984524 + ], + [ + -85.4584326627065, + 36.29041991906133 + ], + [ + -85.45835266197668, + 36.2903679188229 + ], + [ + -85.45811566166371, + 36.290211919522086 + ], + [ + -85.45803666214194, + 36.29015991905115 + ], + [ + -85.45774966251025, + 36.28997091920713 + ], + [ + -85.45688966186809, + 36.289407918692625 + ], + [ + -85.4566056616814, + 36.289221918894846 + ], + [ + -85.45605366171009, + 36.288863918539626 + ], + [ + -85.45677966209131, + 36.28797791881849 + ], + [ + -85.45706266217545, + 36.287635918957776 + ], + [ + -85.45340965991932, + 36.2849639184744 + ], + [ + -85.45345066065299, + 36.28453191839796 + ], + [ + -85.45362066069406, + 36.28273891757441 + ], + [ + -85.45367865979429, + 36.28238391798207 + ], + [ + -85.453852660235, + 36.28132091775301 + ], + [ + -85.45391165994857, + 36.280966917378564 + ], + [ + -85.45396065985497, + 36.280949917235986 + ], + [ + -85.45408466041285, + 36.28091591716372 + ], + [ + -85.45416266057038, + 36.28089491759847 + ], + [ + -85.45434266069907, + 36.280855917789495 + ], + [ + -85.45452466035661, + 36.28082491699517 + ], + [ + -85.45471066011106, + 36.28080591755844 + ], + [ + -85.4549346608378, + 36.28079791741695 + ], + [ + -85.45523466077462, + 36.28080991691688 + ], + [ + -85.45536466046812, + 36.28081991693128 + ], + [ + -85.45645466074085, + 36.28090491696336 + ], + [ + -85.45675366106248, + 36.280926917195906 + ], + [ + -85.45699466143542, + 36.28094891750433 + ], + [ + -85.45717166112377, + 36.280957917258036 + ], + [ + -85.45734966178452, + 36.28097191763524 + ], + [ + -85.45797166131213, + 36.28101291762893 + ], + [ + -85.45840666166042, + 36.28104291718969 + ], + [ + -85.45923366191099, + 36.281088916990754 + ], + [ + -85.45950966149886, + 36.281102917276236 + ], + [ + -85.45959566224631, + 36.281106917508424 + ], + [ + -85.45976966178578, + 36.28111491680567 + ], + [ + -85.46027066171177, + 36.2811409174828 + ], + [ + -85.46037966223909, + 36.28114691693015 + ], + [ + -85.46077966180017, + 36.28116691752396 + ], + [ + -85.4612406628675, + 36.28118591707385 + ], + [ + -85.46150666227341, + 36.28119691671361 + ], + [ + -85.46184666283378, + 36.28120791720786 + ], + [ + -85.46207266246105, + 36.28120491723564 + ], + [ + -85.4622466622205, + 36.28121491728877 + ], + [ + -85.46230266295109, + 36.28121891730878 + ], + [ + -85.4626266624826, + 36.281233917061094 + ], + [ + -85.46320766333045, + 36.28127491689705 + ], + [ + -85.46380466265056, + 36.281323917481636 + ], + [ + -85.46488966327989, + 36.281401916713236 + ], + [ + -85.46581666399477, + 36.28148491730671 + ], + [ + -85.46601566417723, + 36.279389916531834 + ], + [ + -85.46411466348273, + 36.279173916236616 + ], + [ + -85.46345866249533, + 36.27909391621534 + ], + [ + -85.46363566300413, + 36.27789791648499 + ], + [ + -85.46371466321189, + 36.27733591602003 + ], + [ + -85.46343566252499, + 36.27712391634501 + ], + [ + -85.46348466237056, + 36.276962916354535 + ], + [ + -85.46248266270868, + 36.276834916400304 + ], + [ + -85.46210566290765, + 36.27678791661652 + ], + [ + -85.4618216624215, + 36.27675891629372 + ], + [ + -85.46156566198601, + 36.276736916388934 + ], + [ + -85.46129766177258, + 36.27670791581289 + ], + [ + -85.46126766245884, + 36.27670491620677 + ], + [ + -85.4609176625086, + 36.27666091607085 + ], + [ + -85.46065566241896, + 36.27662891631612 + ], + [ + -85.46018666132713, + 36.27658091597219 + ], + [ + -85.4594756611163, + 36.276504916716235 + ], + [ + -85.45916566114197, + 36.27647491607437 + ], + [ + -85.45903366184406, + 36.27646891620268 + ], + [ + -85.45893966119736, + 36.27647991668296 + ], + [ + -85.45832966142464, + 36.27634991610074 + ], + [ + -85.45798366116738, + 36.276314916003464 + ], + [ + -85.45798966131485, + 36.27711491683356 + ], + [ + -85.45365466018944, + 36.2768469164228 + ], + [ + -85.4536596599508, + 36.27681091702226 + ], + [ + -85.4537406602601, + 36.27627291685109 + ], + [ + -85.45376565984101, + 36.27607191672402 + ], + [ + -85.45381066020404, + 36.275689916373544 + ], + [ + -85.45384165945414, + 36.275458916251814 + ], + [ + -85.45389066040296, + 36.27510291623817 + ], + [ + -85.45400166033512, + 36.27421491563987 + ], + [ + -85.45408766005487, + 36.27344291586104 + ], + [ + -85.45419965991866, + 36.27254691537624 + ], + [ + -85.45423266001742, + 36.27191191555476 + ], + [ + -85.45423466021387, + 36.27174591582331 + ], + [ + -85.4561326602693, + 36.27180391567508 + ], + [ + -85.45629766002068, + 36.27181591528239 + ], + [ + -85.4564316602296, + 36.270796915435994 + ], + [ + -85.45654666029068, + 36.269919915461244 + ], + [ + -85.45659466063182, + 36.269551914888666 + ], + [ + -85.45682766059356, + 36.26778991434021 + ], + [ + -85.45625865966407, + 36.26776191488898 + ], + [ + -85.45505965982619, + 36.26770291481015 + ], + [ + -85.4545936600051, + 36.267679915001466 + ], + [ + -85.4536096588716, + 36.267630914435436 + ], + [ + -85.45265165863933, + 36.267586914579525 + ], + [ + -85.45266665902453, + 36.26734591470286 + ], + [ + -85.45267465954728, + 36.26715191462221 + ], + [ + -85.45268365908741, + 36.26690091453925 + ], + [ + -85.45277765916522, + 36.264447913712885 + ], + [ + -85.45289265827722, + 36.261466913247375 + ], + [ + -85.44984665814721, + 36.2612639135475 + ], + [ + -85.44918365769453, + 36.259418913181285 + ], + [ + -85.44858865713043, + 36.25938491336505 + ], + [ + -85.44306065606003, + 36.259044913387285 + ], + [ + -85.4429086560461, + 36.25968591368812 + ], + [ + -85.44044665450005, + 36.25954091388762 + ], + [ + -85.44009965532327, + 36.26098391428824 + ], + [ + -85.43565665340124, + 36.26223991459446 + ], + [ + -85.43563665416536, + 36.26221591424748 + ], + [ + -85.43563065396795, + 36.26220791483906 + ], + [ + -85.43561365382698, + 36.262173914017275 + ], + [ + -85.43558465333903, + 36.262138914457495 + ], + [ + -85.43556565413913, + 36.26211591462867 + ], + [ + -85.4354526538281, + 36.26198091412518 + ], + [ + -85.43543565380575, + 36.26194691417727 + ], + [ + -85.43535965410109, + 36.26184991469924 + ], + [ + -85.43533465360697, + 36.26180191458253 + ], + [ + -85.43526765400603, + 36.26171891445934 + ], + [ + -85.43525065411923, + 36.26168491448273 + ], + [ + -85.43508165378398, + 36.26147191440319 + ], + [ + -85.43505665403096, + 36.26142291385916 + ], + [ + -85.43501465307617, + 36.26136791453855 + ], + [ + -85.43496265347997, + 36.261312914414184 + ], + [ + -85.43494565381111, + 36.26127891439059 + ], + [ + -85.43484565323551, + 36.2611199138922 + ], + [ + -85.43476965297802, + 36.261029913872164 + ], + [ + -85.43476065314628, + 36.261008914546395 + ], + [ + -85.43467665341322, + 36.26090591463792 + ], + [ + -85.43464965324276, + 36.26085691455475 + ], + [ + -85.43462865366017, + 36.26080491440183 + ], + [ + -85.43460965291776, + 36.26075491441627 + ], + [ + -85.43460565329738, + 36.26073091393913 + ], + [ + -85.43457565329994, + 36.26064491417002 + ], + [ + -85.43452965365289, + 36.260392913821136 + ], + [ + -85.43447465326665, + 36.260224913846514 + ], + [ + -85.4343656533854, + 36.25994991382668 + ], + [ + -85.4343306534938, + 36.25981691387752 + ], + [ + -85.43424865279258, + 36.25964691371327 + ], + [ + -85.43417265280915, + 36.2594669135767 + ], + [ + -85.43415965302017, + 36.25939491369244 + ], + [ + -85.43413865291676, + 36.25932391365995 + ], + [ + -85.43397865312973, + 36.2591299141107 + ], + [ + -85.43395365321715, + 36.259081913664275 + ], + [ + -85.43392865357859, + 36.25905491371547 + ], + [ + -85.43391165299167, + 36.2590129141818 + ], + [ + -85.43388565299281, + 36.258985914043315 + ], + [ + -85.43387665355502, + 36.25895791387011 + ], + [ + -85.43385865349678, + 36.258937914120565 + ], + [ + -85.43382665302168, + 36.25887591355583 + ], + [ + -85.43379365309436, + 36.25879291410281 + ], + [ + -85.43377165341587, + 36.258706913711215 + ], + [ + -85.43374365263844, + 36.25849691353489 + ], + [ + -85.4337396525122, + 36.25836291404627 + ], + [ + -85.43372665310767, + 36.258338913705295 + ], + [ + -85.43370265303352, + 36.25816691376506 + ], + [ + -85.43370265265928, + 36.258124913647976 + ], + [ + -85.43372665280708, + 36.257858913924615 + ], + [ + -85.43372065295337, + 36.25769191363417 + ], + [ + -85.43369165318741, + 36.257529913732114 + ], + [ + -85.43367965313756, + 36.257498913686355 + ], + [ + -85.43365865314615, + 36.25740591354736 + ], + [ + -85.43363765237343, + 36.25734791365316 + ], + [ + -85.43362565326484, + 36.257288913220144 + ], + [ + -85.43358765317033, + 36.25718991318589 + ], + [ + -85.43345265257375, + 36.25693491379955 + ], + [ + -85.43340265322122, + 36.256872913406916 + ], + [ + -85.43337665288107, + 36.256824913520425 + ], + [ + -85.43333465330298, + 36.256769913530384 + ], + [ + -85.43323265325364, + 36.25665991378979 + ], + [ + -85.43314865279706, + 36.25655591318521 + ], + [ + -85.433080652334, + 36.256424913659515 + ], + [ + -85.43304465295793, + 36.25633991354436 + ], + [ + -85.43303965304112, + 36.25632891307283 + ], + [ + -85.43300965258202, + 36.25619491313851 + ], + [ + -85.43298865307854, + 36.256142913519476 + ], + [ + -85.43296765314444, + 36.25604991321784 + ], + [ + -85.43293465284852, + 36.25583691334325 + ], + [ + -85.43294865308727, + 36.25557191298771 + ], + [ + -85.43296565263097, + 36.25547591323934 + ], + [ + -85.43300065227895, + 36.25538591274092 + ], + [ + -85.43302565279676, + 36.25533791323088 + ], + [ + -85.43313565278376, + 36.25521091298574 + ], + [ + -85.43325665297691, + 36.25510491338916 + ], + [ + -85.43336665270381, + 36.25503591266927 + ], + [ + -85.43343465239562, + 36.25501591322913 + ], + [ + -85.43348565214454, + 36.25498791311452 + ], + [ + -85.43364265253767, + 36.25499891322773 + ], + [ + -85.4338426529537, + 36.25502291302436 + ], + [ + -85.43399465237961, + 36.25502291313335 + ], + [ + -85.43414265336463, + 36.25500691276521 + ], + [ + -85.43431065312835, + 36.2549819133046 + ], + [ + -85.43440165337783, + 36.2549629132175 + ], + [ + -85.43443465344367, + 36.25494891291636 + ], + [ + -85.43450465334745, + 36.254921913349506 + ], + [ + -85.43452965283149, + 36.254907913431744 + ], + [ + -85.43456365333604, + 36.254900913146344 + ], + [ + -85.43468265346642, + 36.25484691291145 + ], + [ + -85.43486065275111, + 36.254777912615545 + ], + [ + -85.43489565272071, + 36.25475791302257 + ], + [ + -85.4349386527104, + 36.254743913387145 + ], + [ + -85.43497265275725, + 36.2547229132335 + ], + [ + -85.43501465303713, + 36.25470991286974 + ], + [ + -85.43506565313729, + 36.25468191299212 + ], + [ + -85.43514965284436, + 36.25462791267867 + ], + [ + -85.43519265282151, + 36.2545889127528 + ], + [ + -85.43538865322682, + 36.25436691329875 + ], + [ + -85.43544865337454, + 36.25431491312105 + ], + [ + -85.43560565321687, + 36.25420191275134 + ], + [ + -85.43601865281391, + 36.2539749126145 + ], + [ + -85.43624865383468, + 36.253857912676295 + ], + [ + -85.4363756530774, + 36.25376991302484 + ], + [ + -85.43641865359169, + 36.25373491233261 + ], + [ + -85.43648765376138, + 36.25364591293328 + ], + [ + -85.43651365354914, + 36.25358391308881 + ], + [ + -85.43653065391238, + 36.253487912808524 + ], + [ + -85.43653465390155, + 36.25343691265515 + ], + [ + -85.43651565325462, + 36.25324391239194 + ], + [ + -85.43648965396622, + 36.25295791276152 + ], + [ + -85.43649065318588, + 36.252813912062 + ], + [ + -85.43650265330422, + 36.25274791213138 + ], + [ + -85.43654165287823, + 36.25264891237356 + ], + [ + -85.43657665355272, + 36.25260091277368 + ], + [ + -85.43665165363178, + 36.252531912564464 + ], + [ + -85.43676365299856, + 36.25246291264575 + ], + [ + -85.43681065294912, + 36.252424912216874 + ], + [ + -85.43634265381438, + 36.25205591263618 + ], + [ + -85.43587965290487, + 36.251668912549356 + ], + [ + -85.43572765311507, + 36.25079391234598 + ], + [ + -85.43566565330104, + 36.25007191192992 + ], + [ + -85.43585465322339, + 36.247154911073444 + ], + [ + -85.43473765304755, + 36.246645911766876 + ], + [ + -85.4326816522301, + 36.245707911041926 + ], + [ + -85.43232065145114, + 36.24602891172331 + ], + [ + -85.42697865070194, + 36.24561491174985 + ], + [ + -85.42492065005092, + 36.24545591144371 + ], + [ + -85.42266064916305, + 36.24528491135202 + ], + [ + -85.42318264832694, + 36.241665911155444 + ], + [ + -85.41947664763683, + 36.241501911040515 + ], + [ + -85.41947264764654, + 36.241206910567314 + ], + [ + -85.41944364767258, + 36.23896091064432 + ], + [ + -85.41941164752711, + 36.23684590965436 + ], + [ + -85.41940964698875, + 36.23671590991526 + ], + [ + -85.41940464729429, + 36.23643990954037 + ], + [ + -85.41940364699352, + 36.236326910149565 + ], + [ + -85.41940264707708, + 36.236221910150384 + ], + [ + -85.41943664725815, + 36.23615190942576 + ], + [ + -85.4126736455935, + 36.2354419103043 + ], + [ + -85.41248164482464, + 36.23542090968625 + ], + [ + -85.41210464508447, + 36.23464890984399 + ], + [ + -85.4118986450706, + 36.23464591000509 + ], + [ + -85.40713664324282, + 36.2344309099043 + ], + [ + -85.40713564370576, + 36.234421909725185 + ], + [ + -85.4070836440749, + 36.2342869101599 + ], + [ + -85.40703464318467, + 36.234196909768585 + ], + [ + -85.40698564322938, + 36.23413490997874 + ], + [ + -85.40695264359609, + 36.234072910310616 + ], + [ + -85.40692064314229, + 36.23399691027052 + ], + [ + -85.40690664380213, + 36.23381791027732 + ], + [ + -85.40691164368242, + 36.233779909711245 + ], + [ + -85.40690064345056, + 36.23373490996833 + ], + [ + -85.40674364394307, + 36.233664910296824 + ], + [ + -85.40635564331723, + 36.23349891002065 + ], + [ + -85.40628064319966, + 36.233465910230905 + ], + [ + -85.40618364352405, + 36.23342491009418 + ], + [ + -85.40615664374202, + 36.23341391021653 + ], + [ + -85.40612964344993, + 36.23340491018196 + ], + [ + -85.40599164355977, + 36.23336190944513 + ], + [ + -85.40600164302008, + 36.233334909696765 + ], + [ + -85.40607164284638, + 36.23313991008979 + ], + [ + -85.4061706429166, + 36.23286890991814 + ], + [ + -85.40533064344656, + 36.23273390951526 + ], + [ + -85.40495564323696, + 36.23267390945715 + ], + [ + -85.40486964240466, + 36.23265990951176 + ], + [ + -85.40461264237844, + 36.2326179097417 + ], + [ + -85.4045276431238, + 36.232604909457834 + ], + [ + -85.40440964282439, + 36.23258790982942 + ], + [ + -85.40415364222537, + 36.232545909500985 + ], + [ + -85.40236564253706, + 36.23225691005181 + ], + [ + -85.40220064238486, + 36.23218090943184 + ], + [ + -85.4007496412839, + 36.23148690975946 + ], + [ + -85.39923964119093, + 36.230763909777686 + ], + [ + -85.39916864099258, + 36.23072990971296 + ], + [ + -85.399096640809, + 36.23069591003895 + ], + [ + -85.39614264052643, + 36.229281909830156 + ], + [ + -85.39615064019179, + 36.23040390990759 + ], + [ + -85.3957416401413, + 36.23117791002365 + ], + [ + -85.39536363977719, + 36.231145909906694 + ], + [ + -85.3951576403934, + 36.23112890952854 + ], + [ + -85.39495664052804, + 36.231113910262174 + ], + [ + -85.39483163983063, + 36.22946990989822 + ], + [ + -85.3948106402786, + 36.22918790979302 + ], + [ + -85.39407763954885, + 36.2289209094146 + ], + [ + -85.39322763924261, + 36.22861090984091 + ], + [ + -85.39228463918047, + 36.22826790964174 + ], + [ + -85.39110763824506, + 36.22783890945997 + ], + [ + -85.39035263804958, + 36.22756390930617 + ], + [ + -85.3895656385034, + 36.22728790902663 + ], + [ + -85.38791063723623, + 36.22671790916056 + ], + [ + -85.38759863804242, + 36.22660990898199 + ], + [ + -85.38735963723303, + 36.226527909605004 + ], + [ + -85.38583463750672, + 36.22598390940032 + ], + [ + -85.38563863687776, + 36.22603190963579 + ], + [ + -85.38033963549064, + 36.22734890962468 + ], + [ + -85.37857363555293, + 36.227787909939465 + ], + [ + -85.37832063526196, + 36.22722090946676 + ], + [ + -85.37756163479645, + 36.225521909473336 + ], + [ + -85.37730863442123, + 36.22495590952199 + ], + [ + -85.37724063499212, + 36.224975909033695 + ], + [ + -85.37714563443973, + 36.22497390960513 + ], + [ + -85.37701263489147, + 36.224972909168706 + ], + [ + -85.37680163407173, + 36.224942909696026 + ], + [ + -85.37673563482343, + 36.224927909522776 + ], + [ + -85.37665863428003, + 36.22491990927971 + ], + [ + -85.3766516344807, + 36.22491990920567 + ], + [ + -85.37654063420781, + 36.22493590928799 + ], + [ + -85.37649063432077, + 36.22495290911631 + ], + [ + -85.37634063401649, + 36.2248409094938 + ], + [ + -85.37625763403575, + 36.224759909699266 + ], + [ + -85.37620263389276, + 36.22468590931405 + ], + [ + -85.37614163461144, + 36.22460490949759 + ], + [ + -85.37606763391832, + 36.22452090893927 + ], + [ + -85.37594263400487, + 36.22442390936943 + ], + [ + -85.37575063383379, + 36.224296909776534 + ], + [ + -85.3756926338786, + 36.2242439097541 + ], + [ + -85.37559763422075, + 36.2241159097633 + ], + [ + -85.37552663372463, + 36.22403990938972 + ], + [ + -85.37549363420071, + 36.22401190925408 + ], + [ + -85.37546863423442, + 36.223997908942444 + ], + [ + -85.37539363367596, + 36.22397590928602 + ], + [ + -85.37530363423943, + 36.22396090948141 + ], + [ + -85.3753006343627, + 36.22396090928731 + ], + [ + -85.37525863402414, + 36.223946909327026 + ], + [ + -85.3752246342987, + 36.22394690920743 + ], + [ + -85.37515763398521, + 36.22393190929244 + ], + [ + -85.37508263369703, + 36.22389690899434 + ], + [ + -85.37497663348204, + 36.22382890960019 + ], + [ + -85.37495163360916, + 36.223803909493654 + ], + [ + -85.37491263348468, + 36.22376490909859 + ], + [ + -85.37489463368078, + 36.223747909502315 + ], + [ + -85.37485163313862, + 36.22372690966494 + ], + [ + -85.37477563339345, + 36.22367890895722 + ], + [ + -85.37474163362822, + 36.22365790970716 + ], + [ + -85.3747326340073, + 36.22364690939516 + ], + [ + -85.37466063347759, + 36.22356190889996 + ], + [ + -85.37463063368715, + 36.223506909251384 + ], + [ + -85.37459663370164, + 36.22338990940087 + ], + [ + -85.37459263358512, + 36.22335990879224 + ], + [ + -85.37452063350287, + 36.22319790948262 + ], + [ + -85.37443563396161, + 36.22311590935957 + ], + [ + -85.37438563298056, + 36.22307490872437 + ], + [ + -85.37433363301994, + 36.22304690898051 + ], + [ + -85.37425363313343, + 36.223021909501355 + ], + [ + -85.37419363389601, + 36.223012908848226 + ], + [ + -85.37229463295596, + 36.22434390927219 + ], + [ + -85.36952563275159, + 36.22621090969609 + ], + [ + -85.36859563211077, + 36.226175909746395 + ], + [ + -85.36518663118157, + 36.22620091023662 + ], + [ + -85.3653966313705, + 36.22364390934768 + ], + [ + -85.36562963135552, + 36.220594909130305 + ], + [ + -85.34870462551515, + 36.21940390949182 + ], + [ + -85.3406856229968, + 36.2188449098151 + ], + [ + -85.33980862366488, + 36.2187839101424 + ], + [ + -85.33894262282676, + 36.218723909701446 + ], + [ + -85.32671061903656, + 36.217873910196 + ], + [ + -85.32671161911206, + 36.21783891036684 + ], + [ + -85.3267826189346, + 36.21674490965661 + ], + [ + -85.32684661878454, + 36.216060910050714 + ], + [ + -85.32659061906446, + 36.21504690939594 + ], + [ + -85.32649061857305, + 36.21459390915018 + ], + [ + -85.3264626193698, + 36.214422909412036 + ], + [ + -85.32573961839853, + 36.21056290843079 + ], + [ + -85.3256576187462, + 36.210097908974234 + ], + [ + -85.32551461831439, + 36.20996290871657 + ], + [ + -85.32539561810633, + 36.20964990830161 + ], + [ + -85.32482761837075, + 36.208644908664006 + ], + [ + -85.32469961778872, + 36.20841790857719 + ], + [ + -85.32459961783373, + 36.208245907892554 + ], + [ + -85.32437961829488, + 36.20778390795315 + ], + [ + -85.3243726175739, + 36.207709908481405 + ], + [ + -85.3243116183664, + 36.20711590825012 + ], + [ + -85.32417861792638, + 36.20624790831532 + ], + [ + -85.3240716181749, + 36.20558590763942 + ], + [ + -85.32398661814305, + 36.20505090728529 + ], + [ + -85.32373061789967, + 36.20442690770317 + ], + [ + -85.32368361745212, + 36.20416990739875 + ], + [ + -85.32369461798191, + 36.203854907357155 + ], + [ + -85.32428561741179, + 36.20319990767843 + ], + [ + -85.32522261837346, + 36.202406906924985 + ], + [ + -85.32690661874618, + 36.200983906551436 + ], + [ + -85.32675061830906, + 36.20090690710934 + ], + [ + -85.32289261709361, + 36.199686906521045 + ], + [ + -85.31737561530521, + 36.19795090631246 + ], + [ + -85.30817661239082, + 36.19505790632408 + ], + [ + -85.30562661199941, + 36.194326905833904 + ], + [ + -85.29950860954271, + 36.19232690651574 + ], + [ + -85.29767960966204, + 36.19142190584812 + ], + [ + -85.2975576096945, + 36.19137090594406 + ], + [ + -85.29454860785656, + 36.19014690580422 + ], + [ + -85.29101960709302, + 36.18867990544956 + ], + [ + -85.28912860606269, + 36.187893905259926 + ], + [ + -85.28726660573628, + 36.187117906049174 + ], + [ + -85.2750996020756, + 36.18205390552005 + ], + [ + -85.27414660139833, + 36.18166190488018 + ], + [ + -85.27408160177193, + 36.181634905196475 + ], + [ + -85.27405360131587, + 36.181650905447185 + ], + [ + -85.27381560178004, + 36.18177490524479 + ], + [ + -85.2737816013884, + 36.18178190539458 + ], + [ + -85.27365460207736, + 36.18181990548215 + ], + [ + -85.27357060134844, + 36.18185090542934 + ], + [ + -85.273489601793, + 36.18189190484904 + ], + [ + -85.27334160188164, + 36.1820049055623 + ], + [ + -85.27323960106759, + 36.18211890493706 + ], + [ + -85.27320660191701, + 36.18218090501545 + ], + [ + -85.27318060121601, + 36.18220890477915 + ], + [ + -85.27313860150889, + 36.18230390530573 + ], + [ + -85.27311560130457, + 36.18236090497883 + ], + [ + -85.27307960143466, + 36.18245590555766 + ], + [ + -85.27306460111005, + 36.18250890492496 + ], + [ + -85.27304560113082, + 36.182572905332876 + ], + [ + -85.2730326011191, + 36.18260390523835 + ], + [ + -85.27298260187048, + 36.18280290542638 + ], + [ + -85.27296060196842, + 36.18292990573003 + ], + [ + -85.27294560158018, + 36.18324590567283 + ], + [ + -85.27293260192623, + 36.183270905870245 + ], + [ + -85.27291860167583, + 36.18332890511951 + ], + [ + -85.27289860133074, + 36.18338690517994 + ], + [ + -85.27286060130731, + 36.1835699053631 + ], + [ + -85.27285860179232, + 36.183604905505085 + ], + [ + -85.27285560194375, + 36.183675905897076 + ], + [ + -85.27286060181406, + 36.18376890558294 + ], + [ + -85.27283960167507, + 36.18397090577263 + ], + [ + -85.27282960103697, + 36.18402990515826 + ], + [ + -85.27283060120945, + 36.18479690571356 + ], + [ + -85.27255860143562, + 36.18509090576845 + ], + [ + -85.27205660130683, + 36.185459905809005 + ], + [ + -85.2714326015089, + 36.18576690617806 + ], + [ + -85.27048460087956, + 36.18585690604705 + ], + [ + -85.26917160031067, + 36.18536290585475 + ], + [ + -85.26914060039326, + 36.18531890609871 + ], + [ + -85.26604060009946, + 36.18422390589841 + ], + [ + -85.26548259957133, + 36.184456905759376 + ], + [ + -85.26425659949265, + 36.185060906292826 + ], + [ + -85.2654765998562, + 36.1862869067976 + ], + [ + -85.26515259935125, + 36.18658690610742 + ], + [ + -85.26484359970021, + 36.18684290621182 + ], + [ + -85.26253659841065, + 36.18636290653994 + ], + [ + -85.261603598377, + 36.18566590662606 + ], + [ + -85.2603125985409, + 36.18582390681235 + ], + [ + -85.26086559784875, + 36.184687906540226 + ], + [ + -85.2622215981737, + 36.18246690594778 + ], + [ + -85.26165659843883, + 36.18081190577714 + ], + [ + -85.26278659795878, + 36.18125190535486 + ], + [ + -85.26331659818472, + 36.18171090549455 + ], + [ + -85.26338659897912, + 36.18171290535329 + ], + [ + -85.26535259905054, + 36.18176590566605 + ], + [ + -85.26589259975812, + 36.1818189051848 + ], + [ + -85.26591959926898, + 36.18174990563102 + ], + [ + -85.2659355998366, + 36.18168590518202 + ], + [ + -85.2659575990602, + 36.18164090555838 + ], + [ + -85.26597359966699, + 36.181495905087985 + ], + [ + -85.26593559894746, + 36.181306905601915 + ], + [ + -85.26591359895379, + 36.181248905394476 + ], + [ + -85.26588859957826, + 36.18122190567712 + ], + [ + -85.26587159963579, + 36.1811869054306 + ], + [ + -85.26584659918242, + 36.18115990569731 + ], + [ + -85.26581259982653, + 36.1810979056945 + ], + [ + -85.26578759942961, + 36.181070905047136 + ], + [ + -85.26577059955575, + 36.18103590568618 + ], + [ + -85.26568559917517, + 36.18092590515249 + ], + [ + -85.26565959947224, + 36.18090190500851 + ], + [ + -85.26558359913412, + 36.18080890557721 + ], + [ + -85.26546459953578, + 36.18068190481225 + ], + [ + -85.26544859974827, + 36.18065790508224 + ], + [ + -85.26535559861621, + 36.180561905392494 + ], + [ + -85.26528659899353, + 36.180472904906516 + ], + [ + -85.26517659957744, + 36.1803489048434 + ], + [ + -85.26502359851891, + 36.180162905498044 + ], + [ + -85.26499959920157, + 36.18011490505068 + ], + [ + -85.2649725994384, + 36.18008790556927 + ], + [ + -85.26493959952873, + 36.180025905072156 + ], + [ + -85.26490159861656, + 36.17986390518397 + ], + [ + -85.26488859888643, + 36.17983290551057 + ], + [ + -85.26488459903932, + 36.179814905473975 + ], + [ + -85.26486759842685, + 36.1797269054245 + ], + [ + -85.26482059937837, + 36.179592905197815 + ], + [ + -85.26481659845636, + 36.17956890544666 + ], + [ + -85.26478659900091, + 36.179489904630096 + ], + [ + -85.26474359907968, + 36.179441904659946 + ], + [ + -85.26472659890369, + 36.179406905129646 + ], + [ + -85.26443859907903, + 36.17915290521696 + ], + [ + -85.26437159897262, + 36.179070904734466 + ], + [ + -85.2643285992664, + 36.179008905296364 + ], + [ + -85.26431259904957, + 36.17894090485279 + ], + [ + -85.26430759901186, + 36.178860905341324 + ], + [ + -85.26431559924664, + 36.1787449048067 + ], + [ + -85.26433259909909, + 36.17864190485287 + ], + [ + -85.26436159892371, + 36.1783639050198 + ], + [ + -85.2643485981908, + 36.178283904507396 + ], + [ + -85.2643275984684, + 36.178225904639305 + ], + [ + -85.26430259853413, + 36.17817790513387 + ], + [ + -85.26427759883421, + 36.17814990464728 + ], + [ + -85.26426059876572, + 36.1781159045373 + ], + [ + -85.26411559857978, + 36.1779579044222 + ], + [ + -85.26401459885042, + 36.17787190482221 + ], + [ + -85.26373459827704, + 36.17766190464332 + ], + [ + -85.26367459837941, + 36.17762790443466 + ], + [ + -85.2635905987608, + 36.17757990447285 + ], + [ + -85.26348059836718, + 36.177486904368344 + ], + [ + -85.26319259776831, + 36.17712690461571 + ], + [ + -85.26308159778978, + 36.176963905023094 + ], + [ + -85.26043659738974, + 36.17586190457691 + ], + [ + -85.25250259506849, + 36.17255590398793 + ], + [ + -85.25036259425538, + 36.171663904194176 + ], + [ + -85.24997759453713, + 36.171500904298945 + ], + [ + -85.24989459357627, + 36.17140490394543 + ], + [ + -85.24963759435691, + 36.171088903661705 + ], + [ + -85.24926559349996, + 36.170633903658924 + ], + [ + -85.2488325934563, + 36.170455903618134 + ], + [ + -85.24868259384888, + 36.17039390385634 + ], + [ + -85.24857059393571, + 36.17034790362001 + ], + [ + -85.24830759360435, + 36.17023990402303 + ], + [ + -85.24712759287003, + 36.169753903362945 + ], + [ + -85.24543559231795, + 36.16905990373658 + ], + [ + -85.24359059211984, + 36.168323903784014 + ], + [ + -85.24241259216294, + 36.16785690371619 + ], + [ + -85.24184959098054, + 36.16762490371401 + ], + [ + -85.24183459161821, + 36.16761890349282 + ], + [ + -85.24129959136621, + 36.167410903574485 + ], + [ + -85.24102359153284, + 36.16730090360722 + ], + [ + -85.24081459073254, + 36.167214903398325 + ], + [ + -85.23745259019749, + 36.16584090318134 + ], + [ + -85.23674858990229, + 36.16555390307451 + ], + [ + -85.23288958867003, + 36.164025902964156 + ], + [ + -85.23173258817276, + 36.16356690341425 + ], + [ + -85.2311195880245, + 36.1633249034612 + ], + [ + -85.22826258691073, + 36.16219090275369 + ], + [ + -85.22742158694376, + 36.16185690303871 + ], + [ + -85.22710858730221, + 36.161732903143616 + ], + [ + -85.22650358625542, + 36.16148590339113 + ], + [ + -85.22610858603241, + 36.16131990337037 + ], + [ + -85.2253035864818, + 36.160979902586114 + ], + [ + -85.22295758574055, + 36.16005490284937 + ], + [ + -85.22093158436901, + 36.15924490276748 + ], + [ + -85.22042758497157, + 36.159043903094116 + ], + [ + -85.21434658326751, + 36.156579902782966 + ], + [ + -85.20819658117972, + 36.15408390205852 + ], + [ + -85.20793558042361, + 36.153980902408804 + ], + [ + -85.20715258109043, + 36.15367190185107 + ], + [ + -85.20689258099708, + 36.15356890198463 + ], + [ + -85.20611057990483, + 36.15326190196594 + ], + [ + -85.20503558006168, + 36.152838901841335 + ], + [ + -85.20416057928664, + 36.152495902259695 + ], + [ + -85.203740579165, + 36.152456902243664 + ], + [ + -85.2035565794677, + 36.152439902182074 + ], + [ + -85.20323057894166, + 36.15240890217729 + ], + [ + -85.20290557880374, + 36.152378902002546 + ], + [ + -85.20200157940575, + 36.15230790225316 + ], + [ + -85.20033657881287, + 36.15217890265617 + ], + [ + -85.19929057840079, + 36.15208290180318 + ], + [ + -85.19838857787185, + 36.15200090257389 + ], + [ + -85.1943645762782, + 36.15163390237235 + ], + [ + -85.19283557572716, + 36.151490902640674 + ], + [ + -85.19003157500957, + 36.15122890261252 + ], + [ + -85.18293657301058, + 36.15056690232016 + ], + [ + -85.17618057166881, + 36.149950902770875 + ], + [ + -85.1744585713605, + 36.14979390257492 + ], + [ + -85.17062956941797, + 36.14943190261214 + ], + [ + -85.1702655694151, + 36.149396902906275 + ], + [ + -85.16516056797457, + 36.148909903491216 + ], + [ + -85.1610685671053, + 36.148536902938275 + ], + [ + -85.1609075669629, + 36.148521902907326 + ], + [ + -85.15761356577805, + 36.14821590309737 + ], + [ + -85.15678856608233, + 36.14813890293631 + ], + [ + -85.15265556444949, + 36.14776990303166 + ], + [ + -85.1483575632025, + 36.14738490327374 + ], + [ + -85.14804556334985, + 36.14735790301581 + ], + [ + -85.14567156223053, + 36.14713590308025 + ], + [ + -85.1440235614843, + 36.14698090312649 + ], + [ + -85.13774656017618, + 36.14639290388255 + ], + [ + -85.13659855948586, + 36.14628890412297 + ], + [ + -85.13546955977252, + 36.14618690331908 + ], + [ + -85.13381855911335, + 36.146037904230354 + ], + [ + -85.13127855764824, + 36.14580790345667 + ], + [ + -85.13117355844871, + 36.14579890344434 + ], + [ + -85.13028255800117, + 36.1457189039695 + ], + [ + -85.12498255666118, + 36.14520790439789 + ], + [ + -85.12349455523696, + 36.144987904424426 + ], + [ + -85.12320955524729, + 36.14494590431714 + ], + [ + -85.11958755505232, + 36.144725904492816 + ], + [ + -85.1194255543414, + 36.144656904469386 + ], + [ + -85.1189425541705, + 36.14445090378691 + ], + [ + -85.11878155466438, + 36.14438290431061 + ], + [ + -85.1121085526007, + 36.142283904214246 + ], + [ + -85.11194155174687, + 36.14223090366454 + ], + [ + -85.11009055130639, + 36.14163990359956 + ], + [ + -85.10954455186189, + 36.1414689035083 + ], + [ + -85.10271654981673, + 36.139319903351506 + ], + [ + -85.10267954967817, + 36.13933290410028 + ], + [ + -85.10021754814875, + 36.138567903492344 + ], + [ + -85.10059254819306, + 36.138184903608874 + ], + [ + -85.10073854844703, + 36.13807390358642 + ], + [ + -85.10104054886884, + 36.137846903285556 + ], + [ + -85.10170654923311, + 36.13713690315437 + ], + [ + -85.10202254907183, + 36.13681990313453 + ], + [ + -85.10227754915573, + 36.136564902969766 + ], + [ + -85.10234354914284, + 36.136499903052936 + ], + [ + -85.10268254926319, + 36.13611290284641 + ], + [ + -85.10278554879028, + 36.13606690296984 + ], + [ + -85.10339654987486, + 36.13579890286367 + ], + [ + -85.10448954922914, + 36.13538890271019 + ], + [ + -85.10475654967806, + 36.13524790254387 + ], + [ + -85.10544254970391, + 36.13488990278249 + ], + [ + -85.10612055043337, + 36.1339059024853 + ], + [ + -85.10680754999721, + 36.13314690236143 + ], + [ + -85.10732354979184, + 36.132905902588284 + ], + [ + -85.10816555090958, + 36.132906902465855 + ], + [ + -85.10898755053461, + 36.13302890225504 + ], + [ + -85.10981955098389, + 36.133085901910626 + ], + [ + -85.11016155055951, + 36.13308590222537 + ], + [ + -85.11083955093311, + 36.13308790255344 + ], + [ + -85.1115045513437, + 36.132974902068646 + ], + [ + -85.11225855116179, + 36.132757901917564 + ], + [ + -85.11256555168004, + 36.13266990226374 + ], + [ + -85.11326555241861, + 36.132318901993294 + ], + [ + -85.11367755186893, + 36.132113902080356 + ], + [ + -85.1143435522327, + 36.1315329018154 + ], + [ + -85.11466155221409, + 36.13126690162025 + ], + [ + -85.11560355233168, + 36.13111490175197 + ], + [ + -85.11619955271884, + 36.131139901721816 + ], + [ + -85.11638555244483, + 36.13114790126925 + ], + [ + -85.11725355300752, + 36.13144490130837 + ], + [ + -85.11777255342233, + 36.13134290135401 + ], + [ + -85.11842855382409, + 36.13081890108134 + ], + [ + -85.11907555386738, + 36.13043590159262 + ], + [ + -85.11927255360337, + 36.130319901582354 + ], + [ + -85.11961955339669, + 36.13008590105606 + ], + [ + -85.1202935542286, + 36.13014290130414 + ], + [ + -85.12114455414599, + 36.130248901393536 + ], + [ + -85.12173955432492, + 36.13023390106169 + ], + [ + -85.12205755462212, + 36.13006390081973 + ], + [ + -85.12279155471228, + 36.129628901190394 + ], + [ + -85.12331855434923, + 36.12954890085443 + ], + [ + -85.12392255461538, + 36.129549900734894 + ], + [ + -85.12457655457389, + 36.1296069012651 + ], + [ + -85.12496655501067, + 36.12942990093519 + ], + [ + -85.12510655503743, + 36.129376900519894 + ], + [ + -85.12617355565784, + 36.12897890071325 + ], + [ + -85.12665155511144, + 36.128829900765844 + ], + [ + -85.12706855521819, + 36.12875990042004 + ], + [ + -85.12712655554014, + 36.128759901003264 + ], + [ + -85.12753355587, + 36.12876290018435 + ], + [ + -85.12762355614981, + 36.1287639007512 + ], + [ + -85.12813655552328, + 36.12894490025884 + ], + [ + -85.12847055632663, + 36.129220900506695 + ], + [ + -85.12856355645812, + 36.12934390102319 + ], + [ + -85.12873355646998, + 36.12956990032144 + ], + [ + -85.12881855617658, + 36.12966190083923 + ], + [ + -85.12908555607169, + 36.12995090045187 + ], + [ + -85.1291835563088, + 36.1300259007876 + ], + [ + -85.12938055642424, + 36.13017790116616 + ], + [ + -85.12967655704884, + 36.13025290041672 + ], + [ + -85.13034055674336, + 36.130273900296274 + ], + [ + -85.13067455730815, + 36.1302789005759 + ], + [ + -85.13119255748256, + 36.13028790110268 + ], + [ + -85.13157955760926, + 36.13026590043741 + ], + [ + -85.13169455659391, + 36.12892090050077 + ], + [ + -85.13175255757464, + 36.12825289999594 + ], + [ + -85.1339275574866, + 36.125344899579844 + ], + [ + -85.13413055767805, + 36.12507289969073 + ], + [ + -85.13446055778218, + 36.124610899594586 + ], + [ + -85.13547255773734, + 36.123199899221376 + ], + [ + -85.13571555789898, + 36.12285989915691 + ], + [ + -85.13644555771194, + 36.121843898493466 + ], + [ + -85.1366895584545, + 36.12150489859179 + ], + [ + -85.13896555824662, + 36.11839389834599 + ], + [ + -85.14579455993878, + 36.1090618954546 + ], + [ + -85.14604056047403, + 36.10872589606489 + ], + [ + -85.14807056088702, + 36.105950895367044 + ], + [ + -85.14807156011561, + 36.105949894784985 + ], + [ + -85.14890356107097, + 36.104810894731585 + ], + [ + -85.1514035611269, + 36.1013948940695 + ], + [ + -85.152237560899, + 36.100255893474746 + ], + [ + -85.15275456181415, + 36.09955089340349 + ], + [ + -85.15415756195014, + 36.09965489381201 + ], + [ + -85.16098456413829, + 36.10016489315993 + ], + [ + -85.16326056497023, + 36.10033589347082 + ], + [ + -85.16403956481354, + 36.10039289307534 + ], + [ + -85.16637756541783, + 36.10056689328905 + ], + [ + -85.16715756525507, + 36.10062489357998 + ], + [ + -85.16994356671219, + 36.100833892709744 + ], + [ + -85.17543156773844, + 36.10124689256556 + ], + [ + -85.17830456887499, + 36.10146189273975 + ], + [ + -85.18109156923909, + 36.10167089305125 + ], + [ + -85.1863205708331, + 36.10206189299604 + ], + [ + -85.20200857575213, + 36.103237891814096 + ], + [ + -85.20723857728204, + 36.10362989248102 + ], + [ + -85.2072695772261, + 36.10363189251843 + ], + [ + -85.20736257707672, + 36.10363789168154 + ], + [ + -85.20739457771384, + 36.10364089238066 + ], + [ + -85.20955057822285, + 36.10380189196488 + ], + [ + -85.21602058041753, + 36.10428689194417 + ], + [ + -85.21671958020467, + 36.10433989143897 + ], + [ + -85.21817758082759, + 36.104448891782965 + ], + [ + -85.21822958053185, + 36.104452891692894 + ], + [ + -85.21838558077776, + 36.104464892196624 + ], + [ + -85.2184385811889, + 36.10446889219297 + ], + [ + -85.22165058177285, + 36.10470889162359 + ], + [ + -85.22172258137448, + 36.10471389154486 + ], + [ + -85.22407058191662, + 36.10489089171438 + ], + [ + -85.22979958385588, + 36.10531889115429 + ], + [ + -85.23157758467497, + 36.10545189172928 + ], + [ + -85.23383158510948, + 36.105620891423186 + ], + [ + -85.23486358512888, + 36.10569389114129 + ], + [ + -85.23503358550785, + 36.10570589130012 + ], + [ + -85.23554558538255, + 36.105742891712346 + ], + [ + -85.23571658592915, + 36.10575589136067 + ], + [ + -85.23572558558513, + 36.10575589161295 + ], + [ + -85.23575258530758, + 36.10575789141373 + ], + [ + -85.23576158534117, + 36.10575889118737 + ], + [ + -85.2357555855892, + 36.10561889129337 + ], + [ + -85.2357385860448, + 36.10519889160572 + ], + [ + -85.23573358597339, + 36.10506489155149 + ], + [ + -85.23573358596718, + 36.105058890815386 + ], + [ + -85.23572358519803, + 36.10482489155658 + ], + [ + -85.23571558579411, + 36.10454889079586 + ], + [ + -85.23569858545063, + 36.103847890983545 + ], + [ + -85.23570958523123, + 36.10331389048095 + ], + [ + -85.23576058505326, + 36.10302489118585 + ], + [ + -85.23584058582105, + 36.10257789060685 + ], + [ + -85.23585058558294, + 36.10252389053134 + ], + [ + -85.23587258504743, + 36.10235889061055 + ], + [ + -85.23588558544965, + 36.1022628908422 + ], + [ + -85.23595058593683, + 36.10186589016872 + ], + [ + -85.23596058509038, + 36.10179889087007 + ], + [ + -85.23596158574269, + 36.10179889090082 + ], + [ + -85.23597858595758, + 36.10170189079148 + ], + [ + -85.23600358566786, + 36.10151889047609 + ], + [ + -85.23607958586727, + 36.100972890757866 + ], + [ + -85.23610558502605, + 36.10079088993614 + ], + [ + -85.23611358536087, + 36.10074289051247 + ], + [ + -85.23614158596965, + 36.10060588989501 + ], + [ + -85.2361525859807, + 36.10056089001031 + ], + [ + -85.23664358556569, + 36.10121189079854 + ], + [ + -85.23812858581779, + 36.103177890353926 + ], + [ + -85.23818958604113, + 36.103258890903355 + ], + [ + -85.23843658589195, + 36.10358589044427 + ], + [ + -85.23849158686507, + 36.10327889059817 + ], + [ + -85.23851458613639, + 36.10316289027937 + ], + [ + -85.23858558597932, + 36.10281489020275 + ], + [ + -85.23861058671089, + 36.1026998902961 + ], + [ + -85.23861258675562, + 36.10268589068594 + ], + [ + -85.23862058681718, + 36.102645890907915 + ], + [ + -85.23862458637673, + 36.10263289082542 + ], + [ + -85.23872158654973, + 36.10215689060384 + ], + [ + -85.23881058593406, + 36.101726890789706 + ], + [ + -85.23885458618066, + 36.10151489031393 + ], + [ + -85.23889758624, + 36.10130189028642 + ], + [ + -85.2390145869142, + 36.10072889007996 + ], + [ + -85.23911258679973, + 36.10025388977168 + ], + [ + -85.24381058806715, + 36.1005458895756 + ], + [ + -85.2463665885692, + 36.1007048902579 + ], + [ + -85.2469925888706, + 36.095256888465286 + ], + [ + -85.2476855880972, + 36.089216887470776 + ], + [ + -85.24810258761146, + 36.08559688657368 + ], + [ + -85.24822358771472, + 36.08454088639823 + ], + [ + -85.24817358756377, + 36.084480886073145 + ], + [ + -85.24782158834108, + 36.084167886693045 + ], + [ + -85.24776058821672, + 36.08407988671324 + ], + [ + -85.24772658755343, + 36.08399188662385 + ], + [ + -85.24771358784388, + 36.08384388646777 + ], + [ + -85.24773358830313, + 36.08360788605617 + ], + [ + -85.24773358768196, + 36.083574886367195 + ], + [ + -85.24773358804764, + 36.083475886411286 + ], + [ + -85.24769958766501, + 36.08337088624417 + ], + [ + -85.24764458727095, + 36.08327188647131 + ], + [ + -85.24759058797798, + 36.08322288635539 + ], + [ + -85.24729258753855, + 36.08305288594503 + ], + [ + -85.2471985878787, + 36.082980886551304 + ], + [ + -85.2469945873217, + 36.08277288640834 + ], + [ + -85.24678458799234, + 36.08241588653182 + ], + [ + -85.24668358730243, + 36.08230588620672 + ], + [ + -85.24635158755476, + 36.08205288599914 + ], + [ + -85.24616158723938, + 36.08180088635893 + ], + [ + -85.24593158700353, + 36.081624885966335 + ], + [ + -85.24589758671866, + 36.08157588576386 + ], + [ + -85.24588358700701, + 36.081509886209886 + ], + [ + -85.2459105868912, + 36.08125088562108 + ], + [ + -85.24590358732846, + 36.08121288595662 + ], + [ + -85.2458495875009, + 36.08115788605373 + ], + [ + -85.24578858717071, + 36.08111388579785 + ], + [ + -85.24565358667178, + 36.08105388614388 + ], + [ + -85.24561458666871, + 36.08103088553204 + ], + [ + -85.2455785866234, + 36.08100988560901 + ], + [ + -85.24532158665865, + 36.08078988546718 + ], + [ + -85.24522058673494, + 36.0807408856464 + ], + [ + -85.24503058688477, + 36.080718886194816 + ], + [ + -85.24494258696141, + 36.080690885510606 + ], + [ + -85.2448885873706, + 36.08066388544337 + ], + [ + -85.24484758697547, + 36.080624885925054 + ], + [ + -85.24474658678618, + 36.08048288551371 + ], + [ + -85.24469658646892, + 36.08039588560516 + ], + [ + -85.24465458624188, + 36.08032088595918 + ], + [ + -85.24463058672173, + 36.08027888613888 + ], + [ + -85.2445505871746, + 36.08008788616698 + ], + [ + -85.2445185862515, + 36.080009885647826 + ], + [ + -85.24451658689402, + 36.08000588582547 + ], + [ + -85.24450158702926, + 36.07997188541263 + ], + [ + -85.24442158718506, + 36.07986888610092 + ], + [ + -85.2443655865459, + 36.07979588597809 + ], + [ + -85.2442905860835, + 36.07961988571295 + ], + [ + -85.24428458712576, + 36.07953188609301 + ], + [ + -85.24429058624136, + 36.07943288576135 + ], + [ + -85.24430858638993, + 36.079393885432246 + ], + [ + -85.24431158620358, + 36.07938888586868 + ], + [ + -85.24433158702229, + 36.07937288540518 + ], + [ + -85.24437958676027, + 36.07924088541293 + ], + [ + -85.24446058626611, + 36.07909288526325 + ], + [ + -85.24449458631081, + 36.07894988559829 + ], + [ + -85.24452158632293, + 36.078685885697446 + ], + [ + -85.24451058630781, + 36.07859288503289 + ], + [ + -85.24450758705687, + 36.07855988543133 + ], + [ + -85.24449458687914, + 36.07852088547638 + ], + [ + -85.2443855869789, + 36.07835088561648 + ], + [ + -85.24435858669071, + 36.07828488545514 + ], + [ + -85.24434558679238, + 36.07821388527351 + ], + [ + -85.24435158649493, + 36.07814188568049 + ], + [ + -85.24438558641862, + 36.078042885064185 + ], + [ + -85.2445275870581, + 36.077850885053586 + ], + [ + -85.24454758701572, + 36.077778885752224 + ], + [ + -85.244547586481, + 36.07768588549782 + ], + [ + -85.24453458651178, + 36.07761488532631 + ], + [ + -85.24440558700138, + 36.077240885255144 + ], + [ + -85.24426358613181, + 36.07704888524535 + ], + [ + -85.24423558595106, + 36.07695488476895 + ], + [ + -85.24423558688316, + 36.07686188540432 + ], + [ + -85.24426958621986, + 36.07675788534601 + ], + [ + -85.24426758597198, + 36.076744885250974 + ], + [ + -85.244256586444, + 36.0766748853824 + ], + [ + -85.24417458650952, + 36.07655388536243 + ], + [ + -85.24399158637065, + 36.076378884894915 + ], + [ + -85.24392458606084, + 36.07628488490039 + ], + [ + -85.24388758578547, + 36.076204885263245 + ], + [ + -85.24386358649599, + 36.076152885450604 + ], + [ + -85.24385658663154, + 36.07610888483496 + ], + [ + -85.24389058574556, + 36.07590588469284 + ], + [ + -85.24388258602399, + 36.075869884847116 + ], + [ + -85.24386958614635, + 36.07580188495539 + ], + [ + -85.24380258622591, + 36.07575188485633 + ], + [ + -85.24373458633573, + 36.075718884848875 + ], + [ + -85.24347758640617, + 36.07564788489936 + ], + [ + -85.24341658625364, + 36.07562088524857 + ], + [ + -85.24331458601388, + 36.07552688514391 + ], + [ + -85.24324658624484, + 36.07534588524254 + ], + [ + -85.24319958642216, + 36.075268885175625 + ], + [ + -85.24316758566036, + 36.07523188468635 + ], + [ + -85.2431045861056, + 36.0751588849937 + ], + [ + -85.24292858540655, + 36.075037885086424 + ], + [ + -85.24289858632503, + 36.07502188448978 + ], + [ + -85.24279258531978, + 36.07496688457544 + ], + [ + -85.24272558606505, + 36.07490088509586 + ], + [ + -85.2426985855387, + 36.07484088436624 + ], + [ + -85.24268458616683, + 36.07473588466678 + ], + [ + -85.24267858562135, + 36.074593885052764 + ], + [ + -85.24267758617634, + 36.07454988508919 + ], + [ + -85.24268458582165, + 36.074389884588456 + ], + [ + -85.2427115856772, + 36.074279884315544 + ], + [ + -85.2427385862161, + 36.074202884712804 + ], + [ + -85.24292058597085, + 36.073933884652185 + ], + [ + -85.24312358567016, + 36.073603884462266 + ], + [ + -85.24317158545293, + 36.07351088443217 + ], + [ + -85.24320458583047, + 36.07337888429748 + ], + [ + -85.24319158538384, + 36.07329088468934 + ], + [ + -85.24314358532327, + 36.07320888423525 + ], + [ + -85.24311058633795, + 36.073176884098025 + ], + [ + -85.24310358548296, + 36.07316988399704 + ], + [ + -85.24288658535392, + 36.073027884257954 + ], + [ + -85.24275158548849, + 36.0729178848572 + ], + [ + -85.24269658617257, + 36.072854884476655 + ], + [ + -85.24260858511626, + 36.07275288436394 + ], + [ + -85.2425815853996, + 36.07269288447843 + ], + [ + -85.24253458540421, + 36.072571884072865 + ], + [ + -85.24253458601535, + 36.07243988402779 + ], + [ + -85.24255558546731, + 36.07224888416525 + ], + [ + -85.24256158539409, + 36.072203884461715 + ], + [ + -85.24260858546516, + 36.07213188468337 + ], + [ + -85.24269658606201, + 36.0720388846643 + ], + [ + -85.24280458603367, + 36.07196188460334 + ], + [ + -85.24314958574908, + 36.07180188403087 + ], + [ + -85.24337958597734, + 36.07171988381342 + ], + [ + -85.24359658639368, + 36.07165388418823 + ], + [ + -85.24368458638463, + 36.07161488416746 + ], + [ + -85.24375858587608, + 36.07155488364751 + ], + [ + -85.24383958543902, + 36.071460884408516 + ], + [ + -85.24388058547618, + 36.071383883998294 + ], + [ + -85.24393258542348, + 36.07107188378773 + ], + [ + -85.24396158626462, + 36.07090588420898 + ], + [ + -85.24400158622439, + 36.07075788384193 + ], + [ + -85.24408258635835, + 36.070520884105925 + ], + [ + -85.24412158580834, + 36.07044988355529 + ], + [ + -85.24415058582878, + 36.070394883969065 + ], + [ + -85.24422458622466, + 36.07033988351257 + ], + [ + -85.24431258648572, + 36.07029588358315 + ], + [ + -85.24436058593137, + 36.07027388387855 + ], + [ + -85.24446858625404, + 36.07023588380593 + ], + [ + -85.24504358593603, + 36.07009288349993 + ], + [ + -85.24522658671187, + 36.07004788345759 + ], + [ + -85.24528858643454, + 36.07002688404957 + ], + [ + -85.24542258682753, + 36.06998188335836 + ], + [ + -85.24556458641389, + 36.069915883876 + ], + [ + -85.2456525863865, + 36.06985588328534 + ], + [ + -85.24574758621435, + 36.06976788371881 + ], + [ + -85.24582158624739, + 36.0696688840501 + ], + [ + -85.2459095861221, + 36.06952088319957 + ], + [ + -85.24601158609994, + 36.06928288329088 + ], + [ + -85.24604458590336, + 36.069206883888796 + ], + [ + -85.24613258592615, + 36.06908088333645 + ], + [ + -85.24622058677866, + 36.06892188381889 + ], + [ + -85.24626058628219, + 36.0688228830573 + ], + [ + -85.24632858611525, + 36.06845488368246 + ], + [ + -85.24637558609709, + 36.06838288330699 + ], + [ + -85.24653758617997, + 36.068195883433475 + ], + [ + -85.24662558651279, + 36.068069883406 + ], + [ + -85.2466865864511, + 36.067909882878 + ], + [ + -85.2468825870318, + 36.066893883360784 + ], + [ + -85.24699858614433, + 36.06647288287665 + ], + [ + -85.24701758618065, + 36.066404882987776 + ], + [ + -85.24706658706702, + 36.06624688293187 + ], + [ + -85.24721258649865, + 36.065777883016324 + ], + [ + -85.24726658668524, + 36.065525882504296 + ], + [ + -85.2473145862535, + 36.06518988301371 + ], + [ + -85.24731058623699, + 36.06513988240658 + ], + [ + -85.24730058701799, + 36.064992882653954 + ], + [ + -85.24728958614246, + 36.06494588288447 + ], + [ + -85.24725958615693, + 36.06481088237712 + ], + [ + -85.24715758658346, + 36.06442688250561 + ], + [ + -85.24712558607835, + 36.06434888236504 + ], + [ + -85.24698858645998, + 36.06400888278811 + ], + [ + -85.24698158649036, + 36.063937882276605 + ], + [ + -85.24698858591952, + 36.063832882421295 + ], + [ + -85.24702858597855, + 36.06372888275433 + ], + [ + -85.24710958604665, + 36.06360288270892 + ], + [ + -85.24742758590828, + 36.06322888267991 + ], + [ + -85.24744758655565, + 36.06317888228746 + ], + [ + -85.24745458687273, + 36.06312388238427 + ], + [ + -85.24744758677532, + 36.0630468819957 + ], + [ + -85.24738658682335, + 36.062860882161296 + ], + [ + -85.24732058629411, + 36.062691881845964 + ], + [ + -85.24728558629687, + 36.06260188212674 + ], + [ + -85.24723058599417, + 36.062497882120766 + ], + [ + -85.24714258624043, + 36.062365881976014 + ], + [ + -85.2469915857974, + 36.06217088184168 + ], + [ + -85.24675458586239, + 36.06186388237758 + ], + [ + -85.24672258569917, + 36.06182288186441 + ], + [ + -85.24660058594594, + 36.061640882400745 + ], + [ + -85.24645158634098, + 36.061316881467754 + ], + [ + -85.24638358575224, + 36.060970881549835 + ], + [ + -85.24637858616332, + 36.0607888814428 + ], + [ + -85.24637658653772, + 36.06069588184485 + ], + [ + -85.24639758567848, + 36.060519881943435 + ], + [ + -85.24639658622726, + 36.06040288180421 + ], + [ + -85.24639658602675, + 36.06034988138029 + ], + [ + -85.2462755857876, + 36.0602068821161 + ], + [ + -85.24601058528012, + 36.05995488203756 + ], + [ + -85.24590958547907, + 36.05981188131701 + ], + [ + -85.24582858590755, + 36.059674881168306 + ], + [ + -85.24573458516946, + 36.059408881591395 + ], + [ + -85.24571258623165, + 36.059344881120204 + ], + [ + -85.24561058544391, + 36.058877881217526 + ], + [ + -85.24544758602636, + 36.0579708817026 + ], + [ + -85.24538058555079, + 36.057778880965856 + ], + [ + -85.24533958525595, + 36.057718881232816 + ], + [ + -85.2452515857392, + 36.057619881206584 + ], + [ + -85.2451295852188, + 36.05751588140472 + ], + [ + -85.2450145855577, + 36.057438881164515 + ], + [ + -85.24489258550398, + 36.05737788084987 + ], + [ + -85.24454758484991, + 36.057235880760985 + ], + [ + -85.24441858488646, + 36.05715888141468 + ], + [ + -85.24426358469339, + 36.057020880792514 + ], + [ + -85.24414158502798, + 36.056883880963866 + ], + [ + -85.24403258512848, + 36.05674088152609 + ], + [ + -85.2439085850688, + 36.05651288076119 + ], + [ + -85.24382958532877, + 36.0563678809813 + ], + [ + -85.24371458540813, + 36.056180881258314 + ], + [ + -85.243355584682, + 36.05566988098215 + ], + [ + -85.2432945845296, + 36.05552688111727 + ], + [ + -85.24328058449707, + 36.05547188107988 + ], + [ + -85.24326058478397, + 36.055389881208306 + ], + [ + -85.24325358525103, + 36.05524688057028 + ], + [ + -85.24327358466823, + 36.05505988071099 + ], + [ + -85.24330758492252, + 36.05492288051943 + ], + [ + -85.24337558428252, + 36.05477988115247 + ], + [ + -85.24366858489964, + 36.05432788067941 + ], + [ + -85.24376758491057, + 36.05417588063199 + ], + [ + -85.24392958504002, + 36.053938880965966 + ], + [ + -85.24406458486023, + 36.053812880767225 + ], + [ + -85.24413958458922, + 36.053790880131764 + ], + [ + -85.24422658538585, + 36.05379088045402 + ], + [ + -85.24430858474975, + 36.05380688064549 + ], + [ + -85.24437558520911, + 36.05382888038333 + ], + [ + -85.2449245853214, + 36.05413688054491 + ], + [ + -85.24506658542849, + 36.05419688047003 + ], + [ + -85.24520858482484, + 36.05421888082909 + ], + [ + -85.24532358547243, + 36.05420788058285 + ], + [ + -85.24540458478029, + 36.054179880774214 + ], + [ + -85.24558058517886, + 36.05403688035357 + ], + [ + -85.24570858559123, + 36.05389988020832 + ], + [ + -85.2459055859287, + 36.05365288080726 + ], + [ + -85.24595258502764, + 36.05357588002078 + ], + [ + -85.24596558497214, + 36.05352588013261 + ], + [ + -85.2459525856647, + 36.05348188061723 + ], + [ + -85.2459255854935, + 36.053432880553764 + ], + [ + -85.24587758590647, + 36.053377880771 + ], + [ + -85.24581658495933, + 36.05333888047261 + ], + [ + -85.24568858487477, + 36.053295879991154 + ], + [ + -85.24534958474209, + 36.053278880141036 + ], + [ + -85.24528358524167, + 36.05328088052111 + ], + [ + -85.24507958462496, + 36.05328988030338 + ], + [ + -85.24476158515975, + 36.053317880297854 + ], + [ + -85.24466658496651, + 36.053311880791206 + ], + [ + -85.24456558506571, + 36.05329088034115 + ], + [ + -85.24447058454375, + 36.053251880745485 + ], + [ + -85.24441658513649, + 36.05321388079717 + ], + [ + -85.24435558451499, + 36.053136880350266 + ], + [ + -85.24433458460885, + 36.05306488049687 + ], + [ + -85.24435858503283, + 36.05287688069805 + ], + [ + -85.24436158485294, + 36.05285088008396 + ], + [ + -85.24444958455692, + 36.05260888037215 + ], + [ + -85.24454458451008, + 36.05244388045987 + ], + [ + -85.24465258515258, + 36.05232388058784 + ], + [ + -85.24479458502353, + 36.05220788000375 + ], + [ + -85.24487558445526, + 36.052158880424564 + ], + [ + -85.24500458516286, + 36.0521418799806 + ], + [ + -85.24541058547491, + 36.05211387970084 + ], + [ + -85.24559958523794, + 36.05206988020453 + ], + [ + -85.2457415850729, + 36.05197087959736 + ], + [ + -85.24578858541697, + 36.05191588031922 + ], + [ + -85.24584958480247, + 36.051822879984016 + ], + [ + -85.2458565853974, + 36.05179488006076 + ], + [ + -85.24587658475718, + 36.05172387967253 + ], + [ + -85.24586258475419, + 36.051597879637704 + ], + [ + -85.24578858529759, + 36.051492880094926 + ], + [ + -85.24570058489064, + 36.0514328800579 + ], + [ + -85.24549758519468, + 36.05133988024083 + ], + [ + -85.2452405855231, + 36.05124087998082 + ], + [ + -85.24509858509242, + 36.05119687994058 + ], + [ + -85.24469858490092, + 36.05113088032922 + ], + [ + -85.244576584574, + 36.05110387992487 + ], + [ + -85.2444285849198, + 36.05104887995754 + ], + [ + -85.24432658492428, + 36.05099387983257 + ], + [ + -85.2442385842165, + 36.05092288022737 + ], + [ + -85.24416458473458, + 36.050839880222014 + ], + [ + -85.24410958495419, + 36.05075787990189 + ], + [ + -85.24408258466035, + 36.05063687956498 + ], + [ + -85.24408258516877, + 36.05056588018677 + ], + [ + -85.24409658452981, + 36.05051088004503 + ], + [ + -85.244251584887, + 36.05026887967959 + ], + [ + -85.24429258513064, + 36.05018587980755 + ], + [ + -85.2444545846161, + 36.04970287933437 + ], + [ + -85.24453558485915, + 36.049586879619035 + ], + [ + -85.2446035847359, + 36.04952087963603 + ], + [ + -85.24467758493053, + 36.04947187956547 + ], + [ + -85.24475158503931, + 36.04943887921159 + ], + [ + -85.24481958514143, + 36.04942187973263 + ], + [ + -85.24502958433158, + 36.049443879257375 + ], + [ + -85.2451575849312, + 36.04947187939298 + ], + [ + -85.24523958505708, + 36.0495048791961 + ], + [ + -85.24534058524668, + 36.049580879662344 + ], + [ + -85.24541558477182, + 36.04966387982347 + ], + [ + -85.2455705845273, + 36.04987287956306 + ], + [ + -85.24570658534441, + 36.04992187975272 + ], + [ + -85.24584758510254, + 36.049952879795185 + ], + [ + -85.24602458502416, + 36.04999287989597 + ], + [ + -85.24647058560272, + 36.05016288000491 + ], + [ + -85.24680358507749, + 36.05035387991095 + ], + [ + -85.24712058524514, + 36.05053687999656 + ], + [ + -85.2472495858735, + 36.05058587975765 + ], + [ + -85.2473985853242, + 36.0506298795175 + ], + [ + -85.24754058619519, + 36.050618879951834 + ], + [ + -85.2476415857686, + 36.05055287972133 + ], + [ + -85.24770258625972, + 36.05049787966624 + ], + [ + -85.24778358609572, + 36.050392879896314 + ], + [ + -85.2478515860171, + 36.050239879886135 + ], + [ + -85.24788458622507, + 36.05012387941089 + ], + [ + -85.24794558587033, + 36.04956387923086 + ], + [ + -85.24794758595979, + 36.04952187972898 + ], + [ + -85.24795258577561, + 36.04939287907876 + ], + [ + -85.2479455858411, + 36.049200879747815 + ], + [ + -85.24793158616394, + 36.04915687956895 + ], + [ + -85.24788458554895, + 36.04910187954701 + ], + [ + -85.24782358610139, + 36.04905287946922 + ], + [ + -85.2477355858611, + 36.049024879497225 + ], + [ + -85.24763458584783, + 36.049013879616126 + ], + [ + -85.24727558524725, + 36.04897587902412 + ], + [ + -85.24720058597823, + 36.04895387955775 + ], + [ + -85.2471535851356, + 36.048920878914785 + ], + [ + -85.24708558491459, + 36.04883887892783 + ], + [ + -85.2469365854102, + 36.04854787925022 + ], + [ + -85.24671358565335, + 36.04826187957181 + ], + [ + -85.24669258554238, + 36.04821287926255 + ], + [ + -85.24668558539511, + 36.04815787912541 + ], + [ + -85.24669258512408, + 36.04808587887307 + ], + [ + -85.24672658479378, + 36.04798687879175 + ], + [ + -85.24685458568129, + 36.04770687895291 + ], + [ + -85.24691558492013, + 36.04760287952653 + ], + [ + -85.24696958478619, + 36.04752587874953 + ], + [ + -85.24699258563561, + 36.047500879079195 + ], + [ + -85.24703758554189, + 36.04745387928067 + ], + [ + -85.24707758500946, + 36.047420878664006 + ], + [ + -85.24755858549946, + 36.04713487942139 + ], + [ + -85.2475855852548, + 36.0471128788753 + ], + [ + -85.24762558542446, + 36.0470798789823 + ], + [ + -85.24763758549982, + 36.04706387932585 + ], + [ + -85.2476665849316, + 36.047030879237916 + ], + [ + -85.24770658551714, + 36.0469478791443 + ], + [ + -85.24772758547566, + 36.04687087931223 + ], + [ + -85.24773358535106, + 36.04684987876477 + ], + [ + -85.24773358582486, + 36.04679987858992 + ], + [ + -85.24773358501501, + 36.04677287852469 + ], + [ + -85.24769958498786, + 36.04667887854012 + ], + [ + -85.24764558570733, + 36.04660787908683 + ], + [ + -85.24758458519219, + 36.04655287892658 + ], + [ + -85.24739558517183, + 36.04645987851405 + ], + [ + -85.24715158565694, + 36.04641587916337 + ], + [ + -85.2469265852978, + 36.046390879253444 + ], + [ + -85.24689458522995, + 36.04638787895767 + ], + [ + -85.24644758513071, + 36.0463778788228 + ], + [ + -85.24640758472587, + 36.04636687895719 + ], + [ + -85.24631958458687, + 36.04630587883336 + ], + [ + -85.24629858524109, + 36.04626787872739 + ], + [ + -85.24627858501245, + 36.0461468792859 + ], + [ + -85.24627858495953, + 36.04603687904347 + ], + [ + -85.24629858503535, + 36.04599287857432 + ], + [ + -85.24635958514763, + 36.045915878564585 + ], + [ + -85.24645458553249, + 36.04582787903411 + ], + [ + -85.24656958472342, + 36.045761878873655 + ], + [ + -85.24681958506606, + 36.045673879139876 + ], + [ + -85.24726558549293, + 36.04545387853463 + ], + [ + -85.24750958498589, + 36.04534987887191 + ], + [ + -85.24763158571196, + 36.045277878242764 + ], + [ + -85.24781458527029, + 36.04518787815646 + ], + [ + -85.24788858514913, + 36.045151878170685 + ], + [ + -85.24805458568615, + 36.045083878569436 + ], + [ + -85.24817258512881, + 36.045035878225825 + ], + [ + -85.2483145860883, + 36.04494787843174 + ], + [ + -85.24840258567598, + 36.04486587867792 + ], + [ + -85.24847658584004, + 36.04471687856985 + ], + [ + -85.24850358595948, + 36.04462887874071 + ], + [ + -85.24851058568954, + 36.04456887816803 + ], + [ + -85.24846958503939, + 36.04437687842608 + ], + [ + -85.24844658555143, + 36.0443288780633 + ], + [ + -85.24831358518735, + 36.0440528779389 + ], + [ + -85.24824558574068, + 36.043947878368144 + ], + [ + -85.24802258573408, + 36.043706877881846 + ], + [ + -85.24798158508705, + 36.04362987859899 + ], + [ + -85.24799658523982, + 36.04358187823113 + ], + [ + -85.24805558518665, + 36.04340987833046 + ], + [ + -85.24813058587327, + 36.04331587802131 + ], + [ + -85.2483265855963, + 36.043145878237844 + ], + [ + -85.24836658523057, + 36.04308487846207 + ], + [ + -85.24846158540994, + 36.04285487835916 + ], + [ + -85.2484885855814, + 36.042711877894995 + ], + [ + -85.24849558513984, + 36.04260187797454 + ], + [ + -85.24845458555356, + 36.042507877717 + ], + [ + -85.24842058487303, + 36.04238787818416 + ], + [ + -85.2484275850417, + 36.042326878052975 + ], + [ + -85.2484475849596, + 36.04224487771377 + ], + [ + -85.24852858494015, + 36.042057877502764 + ], + [ + -85.24869058569405, + 36.041782877835004 + ], + [ + -85.24872458530025, + 36.04170587768501 + ], + [ + -85.24874458516867, + 36.041601877580945 + ], + [ + -85.24873758505505, + 36.04152987816162 + ], + [ + -85.24868358584126, + 36.04130487751453 + ], + [ + -85.24866958504339, + 36.04114287798957 + ], + [ + -85.24866958566169, + 36.041139877582495 + ], + [ + -85.2487305854278, + 36.0407938776159 + ], + [ + -85.24885858579178, + 36.040491877387055 + ], + [ + -85.24901458594644, + 36.04034587732803 + ], + [ + -85.24908858513044, + 36.04027687747427 + ], + [ + -85.24922358540886, + 36.040172877318305 + ], + [ + -85.2493455858488, + 36.04016687766912 + ], + [ + -85.24944258534954, + 36.040175877453116 + ], + [ + -85.24962958582255, + 36.04019487780673 + ], + [ + -85.24997458611114, + 36.04025487710702 + ], + [ + -85.25012358543636, + 36.04029087742379 + ], + [ + -85.25018058563157, + 36.04031187718892 + ], + [ + -85.25023558554054, + 36.040343877404716 + ], + [ + -85.25033858628868, + 36.04035787723705 + ], + [ + -85.25045458610181, + 36.04039687774511 + ], + [ + -85.25072458561081, + 36.040420877486476 + ], + [ + -85.25088758596277, + 36.04043887762536 + ], + [ + -85.2510185863432, + 36.0404538774238 + ], + [ + -85.25109458616323, + 36.04046587734656 + ], + [ + -85.2511415866217, + 36.04046887783501 + ], + [ + -85.25133658637522, + 36.04050787720897 + ], + [ + -85.25136858613715, + 36.04052787746696 + ], + [ + -85.25202358663414, + 36.03733887696221 + ], + [ + -85.2539905864543, + 36.027770874778575 + ], + [ + -85.25464758612304, + 36.02458287389475 + ], + [ + -85.25468558645333, + 36.02439987421041 + ], + [ + -85.25485858618795, + 36.0235708740708 + ], + [ + -85.25507658563963, + 36.02252187359906 + ], + [ + -85.25636558636758, + 36.01634087281 + ], + [ + -85.25679658655278, + 36.01428287231729 + ], + [ + -85.2570995860831, + 36.01279187130816 + ], + [ + -85.25801158601872, + 36.008320870642166 + ], + [ + -85.25831658615274, + 36.00683087044868 + ], + [ + -85.25851058634603, + 36.00587986977891 + ], + [ + -85.25909158625421, + 36.00302686953747 + ], + [ + -85.25928658612732, + 36.00207586948488 + ], + [ + -85.25932958584058, + 36.00186386916204 + ], + [ + -85.25945858648087, + 36.00122886940717 + ], + [ + -85.25950258643773, + 36.00101786927566 + ], + [ + -85.25965758598792, + 36.00025786879148 + ], + [ + -85.25969658600656, + 36.000071868788446 + ], + [ + -85.26013958638703, + 35.99798386807104 + ], + [ + -85.26030058638742, + 35.99722686848155 + ], + [ + -85.26041158598687, + 35.996651868105374 + ], + [ + -85.2591155851963, + 35.99661486859576 + ], + [ + -85.25501258418255, + 35.99649986802424 + ], + [ + -85.25488558472328, + 35.99649686874575 + ], + [ + -85.25506758438662, + 35.995094868569865 + ], + [ + -85.25531158454646, + 35.993229868183846 + ], + [ + -85.25581758422324, + 35.988847866636085 + ], + [ + -85.25586858361582, + 35.9884148668116 + ], + [ + -85.25701658386619, + 35.97849886512327 + ], + [ + -85.25981758447068, + 35.97875586426584 + ], + [ + -85.26421058566993, + 35.97916086430924 + ], + [ + -85.2642635863067, + 35.97917486459989 + ], + [ + -85.2644225861196, + 35.979216864416905 + ], + [ + -85.26447658594141, + 35.979230864408194 + ], + [ + -85.26769858650248, + 35.98019386429906 + ], + [ + -85.27736659029175, + 35.98308486481075 + ], + [ + -85.2779535897434, + 35.98326086519938 + ], + [ + -85.28058959056166, + 35.98404886513165 + ], + [ + -85.28068759052842, + 35.98407786510684 + ], + [ + -85.28098159085756, + 35.984164864552696 + ], + [ + -85.28108059121288, + 35.98419486521885 + ], + [ + -85.28146459101158, + 35.984308864866925 + ], + [ + -85.28197259094041, + 35.98446086446506 + ], + [ + -85.28261659205681, + 35.98465386497019 + ], + [ + -85.28300059164984, + 35.984768864635186 + ], + [ + -85.28520459196518, + 35.985427864726525 + ], + [ + -85.29181659471432, + 35.98740486537395 + ], + [ + -85.29402059550159, + 35.9880648646565 + ], + [ + -85.29455559525263, + 35.98822386491851 + ], + [ + -85.29599159522542, + 35.98865286503991 + ], + [ + -85.29616359609282, + 35.98870386480825 + ], + [ + -85.29669959583241, + 35.98886386536853 + ], + [ + -85.29820259688262, + 35.989308865104434 + ], + [ + -85.30264159758669, + 35.99062586482206 + ], + [ + -85.30271459819183, + 35.99064686514934 + ], + [ + -85.30421859876385, + 35.99109286487122 + ], + [ + -85.30563559923037, + 35.99147486543096 + ], + [ + -85.30988860013565, + 35.99262086529795 + ], + [ + -85.31130660073799, + 35.99300386525383 + ], + [ + -85.31131460027908, + 35.993740865511874 + ], + [ + -85.31134160082847, + 35.99589886558155 + ], + [ + -85.31134160059275, + 35.995954866041664 + ], + [ + -85.31135160072539, + 35.996692865989665 + ], + [ + -85.31135960109488, + 35.99736786616454 + ], + [ + -85.31138360143892, + 35.9993958665092 + ], + [ + -85.31139260081756, + 36.000071867028936 + ], + [ + -85.3113926014833, + 36.00013186650698 + ], + [ + -85.31139760142578, + 36.00031186647283 + ], + [ + -85.31139960083128, + 36.000371866559775 + ], + [ + -85.31141760077324, + 36.001896866983955 + ], + [ + -85.31147360089442, + 36.00647986841155 + ], + [ + -85.31149460199555, + 36.008009868100935 + ], + [ + -85.31151060132677, + 36.00958286840538 + ], + [ + -85.31157160202766, + 36.01430886948351 + ], + [ + -85.31159460217154, + 36.01588687011352 + ], + [ + -85.31161060220614, + 36.017170869926 + ], + [ + -85.31166060217798, + 36.021026871290125 + ], + [ + -85.31167960278522, + 36.022313870938234 + ], + [ + -85.31168260228026, + 36.022570871481214 + ], + [ + -85.31142260217074, + 36.022569871499265 + ], + [ + -85.31064260246936, + 36.02256987130369 + ], + [ + -85.31055860258328, + 36.02333487175488 + ], + [ + -85.31050260203935, + 36.023849871731734 + ], + [ + -85.31026260192388, + 36.02548287233843 + ], + [ + -85.30954460197817, + 36.030382872708266 + ], + [ + -85.30936760221974, + 36.03159887321602 + ], + [ + -85.30978860206827, + 36.03163687320118 + ], + [ + -85.31073460251409, + 36.031721872808845 + ], + [ + -85.31357560389625, + 36.03198187329197 + ], + [ + -85.31452460428766, + 36.03206887276656 + ], + [ + -85.31728460451227, + 36.03231987336245 + ], + [ + -85.32537460735007, + 36.03305887289039 + ], + [ + -85.32543360711499, + 36.03287287291654 + ], + [ + -85.3262826070484, + 36.030233872162555 + ], + [ + -85.32700860723176, + 36.03059587224152 + ], + [ + -85.32918860792223, + 36.031681872289326 + ], + [ + -85.32991560799896, + 36.0320448723028 + ], + [ + -85.33005160847677, + 36.0321138725651 + ], + [ + -85.33025160796913, + 36.032212872809566 + ], + [ + -85.33031160837429, + 36.03224187278729 + ], + [ + -85.33031360902886, + 36.03224187219038 + ], + [ + -85.33037660903996, + 36.03227387208174 + ], + [ + -85.33056260868828, + 36.03236687251193 + ], + [ + -85.3325046088611, + 36.033335872325644 + ], + [ + -85.33256860927939, + 36.03336787299011 + ], + [ + -85.33266360919437, + 36.033414872453214 + ], + [ + -85.33275860970124, + 36.0334628722765 + ], + [ + -85.33295560941804, + 36.03356087255081 + ], + [ + -85.3331526096206, + 36.03365887250087 + ], + [ + -85.33324460961181, + 36.03370787288616 + ], + [ + -85.33333660972399, + 36.03375687229949 + ], + [ + -85.33337260922798, + 36.03377587240326 + ], + [ + -85.33403561018093, + 36.03412687273376 + ], + [ + -85.33425760973671, + 36.0342438730344 + ], + [ + -85.33638961052802, + 36.03535587256588 + ], + [ + -85.34278561267745, + 36.038691873693786 + ], + [ + -85.34491861320136, + 36.03980387314346 + ], + [ + -85.34692961391173, + 36.04085087337352 + ], + [ + -85.35296261634133, + 36.0439968735066 + ], + [ + -85.3549746163697, + 36.04504587423584 + ], + [ + -85.35505961622744, + 36.04508987396016 + ], + [ + -85.35531961686007, + 36.04522587419807 + ], + [ + -85.35540661670319, + 36.04527387437102 + ], + [ + -85.35662361727447, + 36.045899873891 + ], + [ + -85.3566726170835, + 36.04591187384224 + ], + [ + -85.36081661858262, + 36.04690787409356 + ], + [ + -85.36219961912313, + 36.0472418745461 + ], + [ + -85.36550261958031, + 36.048036874001 + ], + [ + -85.37262562131818, + 36.04975387428744 + ], + [ + -85.37218562173159, + 36.05239487485624 + ], + [ + -85.37237562160232, + 36.05239187447421 + ], + [ + -85.37497662262241, + 36.05235887444684 + ], + [ + -85.3757726231664, + 36.05240887441643 + ], + [ + -85.37640362257073, + 36.05248387423026 + ], + [ + -85.37829662322073, + 36.05271087449662 + ], + [ + -85.37892762392036, + 36.05278687501725 + ], + [ + -85.37900462434744, + 36.05279587455352 + ], + [ + -85.3822366244287, + 36.05311287432595 + ], + [ + -85.39060762779836, + 36.0539358740567 + ], + [ + -85.39212862814384, + 36.0543058744642 + ], + [ + -85.39536062834168, + 36.055091874509316 + ], + [ + -85.39724462883046, + 36.055550874708416 + ], + [ + -85.40289763086308, + 36.056927874761485 + ], + [ + -85.40478263130827, + 36.05738787453699 + ], + [ + -85.40589863187485, + 36.0576588747973 + ], + [ + -85.40925163298134, + 36.05847287422759 + ], + [ + -85.41037063287447, + 36.05874687406447 + ], + [ + -85.41037063322133, + 36.05874787448817 + ], + [ + -85.41037163367014, + 36.05874887439662 + ], + [ + -85.41383763386108, + 36.05959187432929 + ], + [ + -85.42423763811163, + 36.06211987427456 + ], + [ + -85.42770763820519, + 36.06296487446939 + ], + [ + -85.42778663868329, + 36.06298387446456 + ], + [ + -85.42865863907616, + 36.06316987461607 + ], + [ + -85.43062563959376, + 36.063591874173106 + ], + [ + -85.43133163946455, + 36.06375187450547 + ], + [ + -85.43151563937374, + 36.063795874938215 + ], + [ + -85.43246363997646, + 36.064024874194224 + ], + [ + -85.43246663998337, + 36.06402587431827 + ], + [ + -85.43247563966622, + 36.064031875045366 + ], + [ + -85.43247864030509, + 36.06403387469021 + ], + [ + -85.43349763999942, + 36.06428287494687 + ], + [ + -85.43655664143567, + 36.06503087500059 + ], + [ + -85.43757664179368, + 36.06528087453337 + ], + [ + -85.44134164326802, + 36.06622687476009 + ], + [ + -85.45263764580588, + 36.06906487454412 + ], + [ + -85.45640364743737, + 36.07001187455833 + ], + [ + -85.45892864808978, + 36.07062887473795 + ], + [ + -85.46650764990714, + 36.07248187500712 + ], + [ + -85.46903465117138, + 36.07310087491739 + ], + [ + -85.470617651234, + 36.07344187444798 + ] + ] + ] + ] + } + } + ] +} \ No newline at end of file diff --git a/static/js/base.js b/static/js/base.js index 1188f5c..1ef1ecf 100644 --- a/static/js/base.js +++ b/static/js/base.js @@ -1,85 +1,70 @@ (function () { - const config = window.APP_BASE_CONFIG; - const feedbackModal = document.getElementById("feedback-modal"); - const feedbackOpenButton = document.getElementById("feedback-open-btn"); - const feedbackForm = document.getElementById("feedback-form"); - const feedbackStatus = document.getElementById("feedback-status"); - const feedbackPageUrl = document.getElementById("feedback-page-url"); + "use strict"; + var U = window.AppUtils; + var config = window.APP_BASE_CONFIG || {}; - function getCsrfToken() { - const match = document.cookie - .split(";") - .map((value) => value.trim()) - .find((value) => value.startsWith("csrftoken=")); - return match ? decodeURIComponent(match.split("=")[1]) : ""; - } - - function openModal() { - if (!feedbackModal) { - return; - } - if (feedbackPageUrl) { - feedbackPageUrl.value = window.location.pathname + window.location.search; - } - feedbackModal.classList.remove("hidden"); - } - - function closeModal() { - if (!feedbackModal) { - return; - } - feedbackModal.classList.add("hidden"); - } + /* ---- Feedback modal ---- */ + var feedbackOpenBtn = document.getElementById("feedback-open-btn"); + var feedbackForm = document.getElementById("feedback-form"); + var feedbackStatus = document.getElementById("feedback-status"); + var feedbackPageUrl = document.getElementById("feedback-page-url"); function setStatus(message, state) { - if (!feedbackStatus) { - return; - } + if (!feedbackStatus) return; feedbackStatus.textContent = message || ""; feedbackStatus.classList.remove("is-error", "is-success"); - if (state) { - feedbackStatus.classList.add(state); - } + if (state) feedbackStatus.classList.add(state); } - async function submitFeedback(event) { - event.preventDefault(); - const formData = new FormData(feedbackForm); - setStatus(""); - const response = await fetch(config.endpoints.feedbackCreate, { - method: "POST", - headers: { - "X-CSRFToken": getCsrfToken(), - }, - body: formData, - credentials: "same-origin", + if (feedbackOpenBtn) { + feedbackOpenBtn.addEventListener("click", function () { + if (feedbackPageUrl) feedbackPageUrl.value = window.location.pathname + window.location.search; + U.openModal("feedback-modal"); }); - const payload = await response.json(); - if (!response.ok) { - setStatus(payload.error || "Unable to send feedback.", "is-error"); - return; - } - feedbackForm.reset(); - if (feedbackPageUrl) { - feedbackPageUrl.value = window.location.pathname + window.location.search; - } - setStatus("Feedback sent. Thank you.", "is-success"); - window.setTimeout(closeModal, 900); } - if (feedbackOpenButton) { - feedbackOpenButton.addEventListener("click", openModal); + if (feedbackForm) { + feedbackForm.addEventListener("submit", function (e) { + e.preventDefault(); + setStatus(""); + U.fetchJson(config.endpoints.feedbackCreate, { + method: "POST", + headers: { "X-CSRFToken": U.getCsrfToken() }, + body: new FormData(feedbackForm), + }).then(function () { + feedbackForm.reset(); + if (feedbackPageUrl) feedbackPageUrl.value = window.location.pathname + window.location.search; + setStatus("Feedback sent. Thank you.", "is-success"); + setTimeout(function () { U.closeModal("feedback-modal"); }, 900); + }).catch(function (err) { + setStatus(err.message || "Unable to send feedback.", "is-error"); + }); + }); } - document.querySelectorAll('[data-close-modal="feedback-modal"]').forEach((button) => { - button.addEventListener("click", closeModal); + /* ---- Generic close-modal buttons ---- */ + document.querySelectorAll("[data-close-modal]").forEach(function (btn) { + btn.addEventListener("click", function () { + U.closeModal(btn.getAttribute("data-close-modal")); + }); }); - if (feedbackForm) { - feedbackForm.addEventListener("submit", function (event) { - submitFeedback(event).catch(function () { - setStatus("Unable to send feedback.", "is-error"); - }); + /* ---- Hamburger toggle ---- */ + var hamburger = document.getElementById("hamburger-btn"); + var navCollapse = document.getElementById("nav-collapse"); + if (hamburger && navCollapse) { + hamburger.addEventListener("click", function (e) { + e.stopPropagation(); + var open = hamburger.getAttribute("aria-expanded") === "true"; + hamburger.setAttribute("aria-expanded", String(!open)); + navCollapse.classList.toggle("is-open", !open); + }); + navCollapse.addEventListener("click", function (e) { + e.stopPropagation(); + }); + document.addEventListener("click", function () { + hamburger.setAttribute("aria-expanded", "false"); + navCollapse.classList.remove("is-open"); }); } })(); diff --git a/static/js/map.js b/static/js/map.js index b9bff44..5f415d2 100644 --- a/static/js/map.js +++ b/static/js/map.js @@ -1,843 +1,554 @@ (function () { - const config = window.APP_CONFIG; - const rootStyles = getComputedStyle(document.documentElement); + "use strict"; + var U = window.AppUtils; + var config = window.APP_CONFIG || {}; + var isAuth = config.isAuthenticated; + /* ---- CSS colour helpers ---- */ function cssVar(name, fallback) { - const value = rootStyles.getPropertyValue(name).trim(); - return value || fallback; + return getComputedStyle(document.documentElement).getPropertyValue(name).trim() || fallback; } - const MAP_COLORS = { + var COLORS = { pending: cssVar("--map-marker-pending", "#db5a26"), - inProgress: cssVar("--map-marker-in-progress", "#ccb10f"), + in_progress: cssVar("--map-marker-in-progress", "#ccb10f"), cleaned: cssVar("--map-marker-cleaned", "#1f944f"), - route: cssVar("--map-route-color", "#2967ad"), - routeDraw: cssVar("--map-route-draw-color", "#2474cc"), - countyBoundary: cssVar("--map-county-boundary", "#0e5ea8"), - countyBoundaryFill: cssVar("--map-county-boundary-fill", "rgba(14, 94, 168, 0.1)"), - outsideMask: cssVar("--map-outside-mask-color", "#081421"), - outsideMaskOpacity: Number(cssVar("--map-outside-mask-opacity", "0.62")) || 0.62, + district: cssVar("--color-district-accent", "#0e5ea8"), + districtFill: cssVar("--map-county-boundary-fill", "rgba(14,94,168,0.1)"), + maskColor: cssVar("--map-outside-mask-color", "#081421"), + maskOpacity: parseFloat(cssVar("--map-outside-mask-opacity", "0.58")), + route: cssVar("--map-route-draw-color", "#2474cc"), }; - const map = L.map("map").setView([36.1627, -85.5016], 12); - map.createPane("countyMaskPane"); - map.getPane("countyMaskPane").style.zIndex = "340"; - map.createPane("countyBoundaryPane"); - map.getPane("countyBoundaryPane").style.zIndex = "350"; - const trashLayer = L.layerGroup().addTo(map); - const routeLayer = L.layerGroup().addTo(map); - const mapLayerSelect = document.getElementById("map-layer-select"); - const overlayPutnamToggle = document.getElementById("overlay-putnam-toggle"); - const overlaySixCountyToggle = document.getElementById("overlay-six-county-toggle"); - const overlayUpperToggle = document.getElementById("overlay-upper-toggle"); - const overlayCustomToggle = document.getElementById("overlay-custom-toggle"); - const countyBoundaryCheckboxes = Array.from( - document.querySelectorAll('input[name="county-boundary"]') - ); - const customCountyListWrap = document.getElementById("custom-county-list-wrap"); - const drawHandler = new L.Draw.Polyline(map, { - shapeOptions: { color: MAP_COLORS.routeDraw, weight: 5, opacity: 0.92 }, + function statusColor(s) { + return COLORS[(s || "").toLowerCase().replace(/ /g, "_")] || COLORS.pending; + } + + /* ---- Leaflet map init ---- */ + var map = L.map("map", { + center: [36.1627, -85.5016], + zoom: 12, + zoomSnap: 0.25, + zoomDelta: 0.5, + wheelDebounceTime: 80, + zoomControl: true, }); + L.tileLayer("https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png", { + maxZoom: 20, + attribution: '© OpenStreetMap contributors © CARTO', + subdomains: "abcd", + }).addTo(map); - let reportMode = false; - let pendingRouteLayer = null; - let activeBaseLayer = null; - let countyOutsideMaskLayer = null; - let countyBoundariesLoaded = false; - const countyBoundaryLayersByName = new Map(); - const countyBoundaryGeometryByName = new Map(); - const WORLD_RING = [ - [85, -180], - [85, 180], - [-85, 180], - [-85, -180], - ]; - const SIX_COUNTY_PRESET = ["Smith", "Jackson", "Putnam", "White", "Van Buren", "Cumberland"]; - const overlayModeToggles = [ - overlayPutnamToggle, - overlaySixCountyToggle, - overlayUpperToggle, - overlayCustomToggle, - ].filter(Boolean); - - const BASEMAP_CONFIG = { - osm_standard: { - url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", - options: { - maxZoom: 19, - attribution: "© OpenStreetMap contributors", - }, - }, - osm_humanitarian: { - url: "https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png", - options: { - maxZoom: 19, - attribution: "© OpenStreetMap contributors, HOT", - }, - }, - carto_light: { - url: "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png", - options: { - maxZoom: 20, - subdomains: "abcd", - attribution: "© OpenStreetMap contributors, © CARTO", - }, - }, - carto_dark: { - url: "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png", - options: { - maxZoom: 20, - subdomains: "abcd", - attribution: "© OpenStreetMap contributors, © CARTO", - }, - }, - esri_imagery: { - url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", - options: { - maxZoom: 19, - attribution: "Tiles © Esri", - }, - }, - }; + var trashLayer = L.layerGroup().addTo(map); + var areaLayer = L.layerGroup().addTo(map); + var districtPane = map.createPane("districtPane"); + districtPane.style.zIndex = 350; + var maskPane = map.createPane("maskPane"); + maskPane.style.zIndex = 340; - function setBaseLayer(layerKey) { - const selectedKey = BASEMAP_CONFIG[layerKey] ? layerKey : "osm_standard"; - if (activeBaseLayer) { - map.removeLayer(activeBaseLayer); - } - const layerConfig = BASEMAP_CONFIG[selectedKey]; - activeBaseLayer = L.tileLayer(layerConfig.url, layerConfig.options); - activeBaseLayer.addTo(map); - } + var districtBoundaryLayer = null; + var outsideMaskLayer = null; - function buildCountyBoundaryLayer(feature) { - const countyName = feature?.properties?.NAME || "County"; - const layer = L.geoJSON(feature, { - pane: "countyBoundaryPane", - style: { - color: MAP_COLORS.countyBoundary, - weight: 3, - fillColor: MAP_COLORS.countyBoundaryFill, - fillOpacity: 0.12, - }, - }); - layer.bindTooltip(`${countyName} County`, { sticky: true }); - return layer; - } + /* ---- State ---- */ + var currentMode = "report"; // "report" | "cleanup" + var reportSubMode = null; // null | "pin" | "area" + var pendingMarker = null; + var drawControl = null; - function ringToLatLngs(ringCoordinates) { - return ringCoordinates.map((point) => [point[1], point[0]]); - } + /* ---- DOM refs ---- */ + var modeReportBtn = document.getElementById("mode-report-btn"); + var modeCleanupBtn = document.getElementById("mode-cleanup-btn"); + var reportPanel = document.getElementById("report-mode-panel"); + var cleanupPanel = document.getElementById("cleanup-mode-panel"); + var placePinBtn = document.getElementById("place-pin-btn"); + var drawAreaBtn = document.getElementById("draw-area-btn"); + var applyFiltersBtn = document.getElementById("apply-filters-btn"); + var detailContent = document.getElementById("detail-content"); + var trashForm = document.getElementById("trash-form"); + var cleanedForm = document.getElementById("cleaned-form"); - function getOuterRingsFromGeometry(geometry) { - if (!geometry) { - return []; - } - if (geometry.type === "Polygon") { - if (!Array.isArray(geometry.coordinates) || geometry.coordinates.length === 0) { - return []; - } - return [ringToLatLngs(geometry.coordinates[0])]; - } - if (geometry.type === "MultiPolygon") { - const rings = []; - (geometry.coordinates || []).forEach((polygon) => { - if (Array.isArray(polygon) && polygon.length > 0) { - rings.push(ringToLatLngs(polygon[0])); - } - }); - return rings; - } - return []; + /* ---- Helpers ---- */ + function getBBox() { + var b = map.getBounds(); + return b.getWest() + "," + b.getSouth() + "," + b.getEast() + "," + b.getNorth(); } - function buildOutsideMaskLayer(selectedCountyNames) { - const holeRings = []; - selectedCountyNames.forEach((countyName) => { - const geometry = countyBoundaryGeometryByName.get(countyName); - getOuterRingsFromGeometry(geometry).forEach((ring) => { - holeRings.push(ring); - }); - }); - if (holeRings.length === 0) { - return null; - } - return L.polygon([WORLD_RING, ...holeRings], { - pane: "countyMaskPane", - stroke: false, - fillColor: MAP_COLORS.outsideMask, - fillOpacity: MAP_COLORS.outsideMaskOpacity, - interactive: false, - }); + function titleCase(s) { + return (s || "").replace(/_/g, " ").replace(/\b\w/g, function (c) { return c.toUpperCase(); }); } - async function ensureCountyBoundaryLayers() { - if (countyBoundariesLoaded) { - return countyBoundaryLayersByName; - } - const response = await fetch(config.endpoints.countyBoundariesGeoJson, { - credentials: "same-origin", - }); - if (!response.ok) { - throw new Error("Unable to load county boundary overlays."); - } - const geojsonPayload = await response.json(); - (geojsonPayload.features || []).forEach((feature) => { - const countyName = feature?.properties?.NAME; - if (!countyName || countyBoundaryLayersByName.has(countyName)) { - return; - } - countyBoundaryGeometryByName.set(countyName, feature?.geometry || null); - countyBoundaryLayersByName.set(countyName, buildCountyBoundaryLayer(feature)); - }); - countyBoundariesLoaded = true; - return countyBoundaryLayersByName; + function tokenClass(s) { + return (s || "").toLowerCase().replace(/_/g, "-"); } - function syncCustomCountyListVisibility() { - if (!customCountyListWrap) { - return; - } - const isCustomEnabled = overlayCustomToggle && overlayCustomToggle.checked; - customCountyListWrap.classList.toggle("is-hidden", !isCustomEnabled); + function formatDate(iso) { + if (!iso) return "-"; + return new Date(iso).toLocaleString(); } - function ensureOverlayModeSelection(activeToggle) { - if (activeToggle && activeToggle.checked) { - overlayModeToggles.forEach((toggle) => { - if (toggle !== activeToggle) { - toggle.checked = false; - } - }); - return; - } + function setDetail(html) { + if (detailContent) detailContent.innerHTML = html; + } - if (!overlayModeToggles.some((toggle) => toggle.checked) && overlayPutnamToggle) { - overlayPutnamToggle.checked = true; + function setLoading(btnId, loading) { + var btn = document.getElementById(btnId); + if (!btn) return; + var spinner = btn.querySelector(".loading-spinner"); + var label = btn.querySelector(".btn-label"); + if (loading) { + btn.disabled = true; + if (spinner) spinner.classList.remove("hidden"); + if (label) label.textContent = "Submitting\u2026"; + } else { + btn.disabled = false; + if (spinner) spinner.classList.add("hidden"); } } - function getSelectedCountyNames() { - if (overlayUpperToggle && overlayUpperToggle.checked) { - return new Set(Array.from(countyBoundaryLayersByName.keys())); - } + function requireAuth() { + if (isAuth) return true; + U.openModal("auth-gate"); + return false; + } - if (overlaySixCountyToggle && overlaySixCountyToggle.checked) { - return new Set(SIX_COUNTY_PRESET); - } + /* ---- District boundary loading ---- */ + var WORLD_RING = [ + [-90, -180], [90, -180], [90, 180], [-90, 180], [-90, -180], + ]; - if (overlayCustomToggle && overlayCustomToggle.checked) { - const selected = new Set(); - countyBoundaryCheckboxes.forEach((checkbox) => { - if (checkbox.checked) { - selected.add(checkbox.value); + function loadDistricts() { + U.fetchJson(config.endpoints.districts).then(function (data) { + var districts = data.districts || []; + if (!districts.length) return; + + // Find the county boundary (mask source) and inner districts + var county = null; + var innerDistricts = []; + districts.forEach(function (d) { + if (d.slug === "putnam-county") { + county = d; + } else { + innerDistricts.push(d); } }); - return selected; - } - - const selected = new Set(); - if (!overlayPutnamToggle || overlayPutnamToggle.checked) { - selected.add("Putnam"); - } - return selected; - } - async function syncCountyBoundaryVisibility() { - await ensureCountyBoundaryLayers(); - const selectedCountyNames = getSelectedCountyNames(); + // If no separate county entry, use the first district as both + if (!county && districts.length) { + county = districts[0]; + } - countyBoundaryLayersByName.forEach((layer, countyName) => { - if (selectedCountyNames.has(countyName)) { - if (!map.hasLayer(layer)) { - layer.addTo(map); + // Build outside mask from county boundary + if (county) { + var geom = county.geometry; + var holes = []; + if (geom.type === "MultiPolygon") { + geom.coordinates.forEach(function (poly) { + holes.push(poly[0].map(function (c) { return [c[1], c[0]]; })); + }); + } else if (geom.type === "Polygon") { + holes.push(geom.coordinates[0].map(function (c) { return [c[1], c[0]]; })); } - } else if (map.hasLayer(layer)) { - map.removeLayer(layer); - } - }); - if (countyOutsideMaskLayer && map.hasLayer(countyOutsideMaskLayer)) { - map.removeLayer(countyOutsideMaskLayer); - countyOutsideMaskLayer = null; - } - countyOutsideMaskLayer = buildOutsideMaskLayer(selectedCountyNames); - if (countyOutsideMaskLayer) { - countyOutsideMaskLayer.addTo(map); - } - } + var worldLatLngs = WORLD_RING.map(function (c) { return [c[0], c[1]]; }); + var maskCoords = [worldLatLngs].concat(holes); + outsideMaskLayer = L.polygon(maskCoords, { + pane: "maskPane", + color: "transparent", + fillColor: COLORS.maskColor, + fillOpacity: COLORS.maskOpacity, + interactive: false, + }).addTo(map); + + // County outline + var countyLayer = L.geoJSON(geom, { + pane: "districtPane", + style: { + color: COLORS.district, + weight: 2, + fillOpacity: 0, + dashArray: "6 4", + }, + }); + countyLayer.bindTooltip(county.name, { sticky: true }); + countyLayer.addTo(map); - setBaseLayer(mapLayerSelect ? mapLayerSelect.value : "osm_humanitarian"); - if (mapLayerSelect) { - mapLayerSelect.addEventListener("change", function () { - setBaseLayer(mapLayerSelect.value); - }); - } - function handleOverlayModeToggleChange(activeToggle) { - ensureOverlayModeSelection(activeToggle); - syncCustomCountyListVisibility(); - syncCountyBoundaryVisibility().catch((error) => { - setDetailHtml(renderDetailNotice("Overlay error", error.message)); - }); - } + // Fit map to county + map.fitBounds(countyLayer.getBounds(), { padding: [30, 30] }); + } - overlayModeToggles.forEach((toggle) => { - toggle.addEventListener("change", function () { - handleOverlayModeToggleChange(toggle); - }); - }); - countyBoundaryCheckboxes.forEach((checkbox) => { - checkbox.addEventListener("change", function () { - syncCountyBoundaryVisibility().catch((error) => { - setDetailHtml(renderDetailNotice("Overlay error", error.message)); + // Render inner district boundaries (e.g. District 3) + innerDistricts.forEach(function (d) { + var layer = L.geoJSON(d.geometry, { + pane: "districtPane", + style: { + color: COLORS.district, + weight: 3, + fillColor: COLORS.districtFill, + fillOpacity: 0.12, + }, + }); + layer.bindTooltip(d.name, { permanent: true, direction: "center", className: "district-label" }); + layer.addTo(map); + districtBoundaryLayer = layer; }); + }).catch(function () { + // Districts may not be seeded yet }); - }); - - function getCsrfToken() { - const match = document.cookie - .split(";") - .map((v) => v.trim()) - .find((v) => v.startsWith("csrftoken=")); - return match ? decodeURIComponent(match.split("=")[1]) : ""; - } - - function statusColor(status) { - if (status === "CLEANED") { - return MAP_COLORS.cleaned; - } - if (status === "IN_PROGRESS") { - return MAP_COLORS.inProgress; - } - return MAP_COLORS.pending; } - function openModal(id) { - document.getElementById(id).classList.remove("hidden"); + /* ---- Feature loading ---- */ + function getStatusFilters() { + var checks = document.querySelectorAll('input[name="status"]:checked'); + return Array.from(checks).map(function (c) { return c.value; }).join(","); } - function closeModal(id) { - document.getElementById(id).classList.add("hidden"); - } + function loadFeatures() { + var url = config.endpoints.features + "?bbox=" + getBBox(); + var statuses = getStatusFilters(); + if (statuses) url += "&status=" + statuses; - function getFilterStatusCsv() { - const selected = []; - document.querySelectorAll('input[name="status"]:checked').forEach((item) => selected.push(item.value)); - return selected.join(","); - } + U.fetchJson(url).then(function (data) { + trashLayer.clearLayers(); + areaLayer.clearLayers(); + var features = data.features || []; - function getDaysFilter() { - return document.getElementById("days-filter").value; - } + features.forEach(function (f) { + var p = f.properties; + var coords = f.geometry.coordinates; + var marker = L.circleMarker([coords[1], coords[0]], { + radius: 8, + fillColor: statusColor(p.status), + color: "#fff", + weight: 2, + fillOpacity: 0.9, + }); - function getBBoxString() { - const bounds = map.getBounds(); - return [ - bounds.getWest().toFixed(6), - bounds.getSouth().toFixed(6), - bounds.getEast().toFixed(6), - bounds.getNorth().toFixed(6), - ].join(","); - } + var popup = '
' + + '' + U.escapeHtml(p.title || "Trash Site") + '' + + '' + titleCase(p.status) + ''; + if (p.can_mark_cleaned && p.status !== "CLEANED") { + popup += ''; + } + popup += '
'; + marker.bindPopup(popup); - function escapeHtml(text) { - if (!text) { - return ""; - } - return String(text) - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll('"', """) - .replaceAll("'", "'"); - } + marker.on("click", function () { + showDetail(p.id); + }); - function titleCaseToken(value) { - if (!value) { - return ""; - } - return String(value) - .toLowerCase() - .split("_") - .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) - .join(" "); - } + marker.addTo(trashLayer); - function tokenClass(value) { - return String(value || "") - .toLowerCase() - .replaceAll("_", "-"); - } + // Render area polygon if present + if (p.area_geojson) { + L.geoJSON(p.area_geojson, { + style: { + color: statusColor(p.status), + weight: 2, + fillColor: statusColor(p.status), + fillOpacity: 0.15, + dashArray: "5 5", + }, + interactive: false, + }).addTo(areaLayer); + } + }); - function formatDateTime(value) { - if (!value) { - return "n/a"; - } - const parsed = new Date(value); - if (Number.isNaN(parsed.getTime())) { - return String(value); - } - return parsed.toLocaleString(); + U.announce(features.length + " report" + (features.length !== 1 ? "s" : "") + " loaded."); + }).catch(function () { + U.showToast("Failed to load map features.", "error"); + }); } - function renderDetailNotice(title, message) { - return ` -
- ${escapeHtml(title)} -

${escapeHtml(message)}

-
- `; + /* ---- Detail panel ---- */ + function renderBadge(label, cls) { + return '' + U.escapeHtml(label) + ''; } - function renderDetailBadge(label, modifierClass) { - return `${escapeHtml(label)}`; + function renderMeta(label, value) { + return '
' + U.escapeHtml(label) + '
' + U.escapeHtml(value) + '
'; } - function renderMetaItem(label, value) { - return ` -
-
${escapeHtml(label)}
-
${escapeHtml(value)}
-
- `; - } + function showDetail(siteId) { + setDetail('

Loading\u2026

'); + var url = config.endpoints.trashUpdateBase + siteId + "/detail/"; + U.fetchJson(url).then(function (site) { + var html = '
'; + html += '
'; + html += 'Trash Site'; + html += '

' + U.escapeHtml(site.title || "Unnamed Site") + '

'; + html += '
'; + html += renderBadge(titleCase(site.status), tokenClass(site.status)); + if (site.severity) html += renderBadge(titleCase(site.severity), tokenClass(site.severity)); + if (site.hazard_flag) html += renderBadge("Hazard", "hazard"); + html += '
'; - function renderProofs(proofs) { - if (!proofs || proofs.length === 0) { - return '
No proof entries yet.
'; - } - return ` -
- ${proofs - .map((proof) => { - const photos = (proof.photos || []) - .map((url) => `proof photo`) - .join(""); - return ` -
-
- ${escapeHtml(String(proof.bags_count))} bags - ${escapeHtml(formatDateTime(proof.created_at))} -
-

${escapeHtml(proof.note || "No note added.")}

-

By ${escapeHtml(proof.created_by)}

- ${photos ? `
${photos}
` : ""} -
- `; - }) - .join("")} -
- `; - } + html += '
'; + html += renderMeta("Reported by", site.created_by); + html += renderMeta("Created", formatDate(site.created_at)); + if (site.cleaned_at) html += renderMeta("Cleaned", formatDate(site.cleaned_at)); + html += '
'; - async function fetchJson(url, options) { - const response = await fetch(url, options); - if (!response.ok) { - let message = "Request failed."; - try { - const payload = await response.json(); - message = payload.error || message; - } catch (_error) {} - throw new Error(message); - } - return response.json(); - } - - function setDetailHtml(html) { - document.getElementById("detail-content").innerHTML = html; - } - - function scrollDetailIntoView() { - const detailContent = document.getElementById("detail-content"); - if (detailContent) { - detailContent.scrollIntoView({ behavior: "smooth", block: "start" }); - } - } + if (site.description) { + html += '

Description

'; + html += '

' + U.escapeHtml(site.description) + '

'; + } - function getFocusRequest() { - const params = new URLSearchParams(window.location.search); - const focusType = params.get("focus_type"); - const focusId = params.get("focus_id"); - if (!focusType || !focusId) { - return null; - } - return { focusType, focusId }; - } + // Photos by type + var photos = site.photos || {}; + ["report", "before", "after"].forEach(function (type) { + var urls = photos[type] || []; + if (urls.length) { + html += '

' + titleCase(type) + ' Photos

'; + html += '
'; + urls.forEach(function (u) { + html += '' + type + ' photo'; + }); + html += '
'; + } + }); - async function showTrashSiteDetail(siteId) { - try { - const site = await fetchJson(`${config.endpoints.trashUpdateBase}${siteId}/detail/`); - const badges = [renderDetailBadge(titleCaseToken(site.status), `detail-badge--${tokenClass(site.status)}`)]; - if (site.severity) { - badges.push( - renderDetailBadge( - `${titleCaseToken(site.severity)} severity`, - `detail-badge--${tokenClass(site.severity)}` - ) - ); - } - if (site.hazard_flag) { - badges.push(renderDetailBadge("Hazard flagged", "detail-badge--hazard")); - } - const cleanedButton = - site.status !== "CLEANED" && site.permissions && site.permissions.can_mark_cleaned - ? `` - : ""; - setDetailHtml(` -
-
-
- Trash Site -

${escapeHtml(site.title || "Trash Site")}

-
-
${badges.join("")}
-
-
- ${renderMetaItem("Reported by", site.created_by)} - ${renderMetaItem("Created", formatDateTime(site.created_at))} - ${renderMetaItem("Last updated", formatDateTime(site.updated_at))} - ${renderMetaItem("Cleaned at", site.cleaned_at ? formatDateTime(site.cleaned_at) : "Not yet cleaned")} -
-
-

Description

-

${escapeHtml( - site.description || "No description added yet." - )}

-
- ${cleanedButton ? `
${cleanedButton}
` : ""} -
-

Cleanup Proofs

- ${renderProofs(site.proofs)} -
-
- `); - scrollDetailIntoView(); - const cleanedBtn = document.getElementById("detail-mark-cleaned-btn"); - if (cleanedBtn) { - cleanedBtn.addEventListener("click", function () { - document.getElementById("cleaned-site-id").value = cleanedBtn.dataset.siteId; - openModal("cleaned-modal"); + // Proofs + if (site.proofs && site.proofs.length) { + html += '

Proof History

'; + site.proofs.forEach(function (proof) { + html += '
'; + html += '
'; + if (proof.bags_count) html += '' + proof.bags_count + ' bags'; + html += '' + U.escapeHtml(proof.created_by) + ' · ' + formatDate(proof.created_at) + ''; + html += '
'; + if (proof.note) html += '

' + U.escapeHtml(proof.note) + '

'; + if (proof.photos && proof.photos.length) { + html += '
'; + proof.photos.forEach(function (ph) { + html += '' + (ph.type || '; + }); + html += '
'; + } + html += '
'; }); + html += '
'; } - } catch (error) { - setDetailHtml(renderDetailNotice("Unable to load details", error.message)); - } - } - async function showRouteDetail(routeId) { - try { - const route = await fetchJson(`/api/route-cleanups/${routeId}/detail/`); - const badges = [ - renderDetailBadge(titleCaseToken(route.status), `detail-badge--${tokenClass(route.status)}`), - ]; - setDetailHtml(` -
-
-
- Cleanup Route -

Cleanup Route

-
-
${badges.join("")}
-
-
- ${renderMetaItem("Distance", `${Number(route.distance_miles).toFixed(2)} miles`)} - ${renderMetaItem("Time", route.time_spent_minutes ? `${route.time_spent_minutes} minutes` : "n/a")} - ${renderMetaItem("Logged by", route.created_by)} - ${renderMetaItem("Created", formatDateTime(route.created_at))} -
-
-

Route Notes

-

${escapeHtml( - route.notes || "No notes added for this route." - )}

-
-
-

Proofs

- ${renderProofs(route.proofs)} -
-
- `); - scrollDetailIntoView(); - } catch (error) { - setDetailHtml(renderDetailNotice("Unable to load details", error.message)); - } - } + // Mark cleaned button + if (site.permissions && site.permissions.can_mark_cleaned && site.status !== "CLEANED") { + html += '
'; + html += ''; + html += '
'; + } - function buildTrashPopup(siteProps) { - const title = escapeHtml(siteProps.title || "Trash Site"); - const cleanedButton = - siteProps.status !== "CLEANED" && siteProps.can_mark_cleaned - ? `` - : ""; - return ` -
- ${title} - ${escapeHtml( - titleCaseToken(siteProps.status) - )} - ${cleanedButton} -
- `; - } + html += '
'; + setDetail(html); - function attachPopupCleanHandler(layer) { - layer.on("popupopen", function (event) { - const popupEl = event.popup.getElement(); - const btn = popupEl ? popupEl.querySelector(".popup-clean-btn") : null; - if (btn) { - btn.addEventListener("click", function () { - document.getElementById("cleaned-site-id").value = btn.dataset.siteId; - openModal("cleaned-modal"); + // Bind mark cleaned button in detail panel + var cleanBtn = detailContent.querySelector("[data-open-cleaned]"); + if (cleanBtn) { + cleanBtn.addEventListener("click", function () { + openCleanedModal(cleanBtn.getAttribute("data-open-cleaned")); }); } + }).catch(function () { + setDetail('

Unable to load details.

'); }); } - async function loadFeatures() { - const params = new URLSearchParams({ - bbox: getBBoxString(), - status: getFilterStatusCsv(), - days: getDaysFilter(), - }); - const payload = await fetchJson(`${config.endpoints.features}?${params.toString()}`); - - trashLayer.clearLayers(); - routeLayer.clearLayers(); - - (payload.features || []).forEach((feature) => { - const kind = feature.properties.type; - if (kind === "trash_site") { - const marker = L.circleMarker( - [feature.geometry.coordinates[1], feature.geometry.coordinates[0]], - { - radius: 8, - color: "#ffffff", - fillColor: statusColor(feature.properties.status), - fillOpacity: 0.95, - weight: 2.5, - } - ); - marker.bindPopup(buildTrashPopup(feature.properties)); - attachPopupCleanHandler(marker); - marker.on("click", function () { - showTrashSiteDetail(feature.properties.id); - }); - marker.addTo(trashLayer); - } + /* ---- Mode switching ---- */ + function setMode(mode) { + currentMode = mode; + cancelReportSubMode(); - if (kind === "route_cleanup") { - const routePoints = feature.geometry.coordinates.map((coord) => [coord[1], coord[0]]); - const routeGroup = L.layerGroup(); - const polyline = L.polyline(routePoints, { - color: MAP_COLORS.route, - weight: 5, - opacity: 0.9, - lineCap: "round", - lineJoin: "round", - }); - polyline.on("click", function () { - showRouteDetail(feature.properties.id); - }); - polyline.bindPopup( - `Cleanup Route
${Number(feature.properties.distance_miles).toFixed( - 2 - )} miles` - ); - polyline.addTo(routeGroup); - - if (routePoints.length >= 2) { - const startMarker = L.circleMarker(routePoints[0], { - radius: 5.5, - color: "#ffffff", - weight: 2, - fillColor: MAP_COLORS.route, - fillOpacity: 1, - }).bindTooltip("Start", { direction: "top", opacity: 0.92 }); - const endMarker = L.circleMarker(routePoints[routePoints.length - 1], { - radius: 5.5, - color: "#ffffff", - weight: 2, - fillColor: MAP_COLORS.cleaned, - fillOpacity: 1, - }).bindTooltip("End", { direction: "top", opacity: 0.92 }); - [startMarker, endMarker].forEach((endpointMarker) => { - endpointMarker.on("click", function () { - showRouteDetail(feature.properties.id); - }); - endpointMarker.addTo(routeGroup); - }); - } + modeReportBtn.classList.toggle("is-active", mode === "report"); + modeCleanupBtn.classList.toggle("is-active", mode === "cleanup"); + modeReportBtn.setAttribute("aria-pressed", mode === "report"); + modeCleanupBtn.setAttribute("aria-pressed", mode === "cleanup"); - routeGroup.addTo(routeLayer); - } - }); - } + reportPanel.classList.toggle("hidden", mode !== "report"); + cleanupPanel.classList.toggle("hidden", mode !== "cleanup"); - async function focusRequestedFeature() { - const focusRequest = getFocusRequest(); - if (!focusRequest) { - return; + if (mode === "report") { + U.announce("Report mode. Place a pin or draw an area on the map."); + } else { + U.announce("Cleanup mode. Click an existing report to start a cleanup."); + loadFeatures(); } + } - if (focusRequest.focusType === "trash_site") { - const site = await fetchJson(`${config.endpoints.trashUpdateBase}${focusRequest.focusId}/detail/`); - map.setView([site.coordinates[1], site.coordinates[0]], 15); - await loadFeatures(); - await showTrashSiteDetail(focusRequest.focusId); - return; - } + modeReportBtn.addEventListener("click", function () { setMode("report"); }); + modeCleanupBtn.addEventListener("click", function () { setMode("cleanup"); }); - if (focusRequest.focusType === "route_cleanup") { - const route = await fetchJson(`/api/route-cleanups/${focusRequest.focusId}/detail/`); - const bounds = L.latLngBounds(route.coordinates.map((coord) => [coord[1], coord[0]])); - map.fitBounds(bounds.pad(0.2)); - await loadFeatures(); - await showRouteDetail(focusRequest.focusId); - } + /* ---- Report mode: pin placement ---- */ + function cancelReportSubMode() { + reportSubMode = null; + if (pendingMarker) { map.removeLayer(pendingMarker); pendingMarker = null; } + if (drawControl) { drawControl.disable(); drawControl = null; } + map.getContainer().style.cursor = ""; + if (placePinBtn) placePinBtn.classList.remove("is-active"); + if (drawAreaBtn) drawAreaBtn.classList.remove("is-active"); } - async function handleTrashCreate(event) { - event.preventDefault(); - const form = event.currentTarget; - const formData = new FormData(form); - await fetchJson(config.endpoints.trashCreate, { - method: "POST", - headers: { "X-CSRFToken": getCsrfToken() }, - body: formData, - credentials: "same-origin", + placePinBtn.addEventListener("click", function () { + if (!requireAuth()) return; + cancelReportSubMode(); + reportSubMode = "pin"; + placePinBtn.classList.add("is-active"); + map.getContainer().style.cursor = "crosshair"; + U.announce("Click on the map to place a pin for your report."); + }); + + drawAreaBtn.addEventListener("click", function () { + if (!requireAuth()) return; + cancelReportSubMode(); + reportSubMode = "area"; + drawAreaBtn.classList.add("is-active"); + drawControl = new L.Draw.Polygon(map, { + shapeOptions: { color: COLORS.route, weight: 3, fillOpacity: 0.15 }, }); - form.reset(); - closeModal("trash-modal"); - await loadFeatures(); - } + drawControl.enable(); + U.announce("Draw a polygon on the map to define the area."); + }); - async function handleMarkCleaned(event) { - event.preventDefault(); - const siteId = document.getElementById("cleaned-site-id").value; - if (!siteId) { - return; - } - const formData = new FormData(event.currentTarget); - await fetchJson(`${config.endpoints.trashUpdateBase}${siteId}/mark-cleaned/`, { - method: "POST", - headers: { "X-CSRFToken": getCsrfToken() }, - body: formData, - credentials: "same-origin", + map.on("click", function (e) { + if (currentMode !== "report" || reportSubMode !== "pin") return; + if (!requireAuth()) return; + + if (pendingMarker) map.removeLayer(pendingMarker); + pendingMarker = L.circleMarker(e.latlng, { + radius: 10, + fillColor: COLORS.pending, + color: "#fff", + weight: 3, + fillOpacity: 0.9, + }).addTo(map); + + var geojson = JSON.stringify({ + type: "Point", + coordinates: [e.latlng.lng, e.latlng.lat], }); - event.currentTarget.reset(); - closeModal("cleaned-modal"); - await showTrashSiteDetail(siteId); - await loadFeatures(); - } + document.getElementById("trash-geojson").value = geojson; + U.openModal("trash-modal"); + cancelReportSubMode(); + }); - async function handleRouteCreate(event) { - event.preventDefault(); - const form = event.currentTarget; - const coords = document.getElementById("route-coordinates").value; - if (!coords) { - alert("Draw a route first."); - return; - } - const formData = new FormData(form); - formData.set("coordinates", coords); + map.on(L.Draw.Event.CREATED, function (e) { + if (currentMode !== "report" || reportSubMode !== "area") return; + if (!requireAuth()) return; + + var layer = e.layer; + var latlngs = layer.getLatLngs()[0]; + var coords = latlngs.map(function (ll) { return [ll.lng, ll.lat]; }); + coords.push(coords[0]); // close the ring + + var geojson = JSON.stringify({ + type: "Polygon", + coordinates: [coords], + }); + document.getElementById("trash-geojson").value = geojson; + U.openModal("trash-modal"); + cancelReportSubMode(); + }); - await fetchJson(config.endpoints.routeCreate, { + /* ---- Report form submission ---- */ + trashForm.addEventListener("submit", function (e) { + e.preventDefault(); + if (!requireAuth()) return; + setLoading("trash-submit-btn", true); + + var formData = new FormData(trashForm); + U.fetchJson(config.endpoints.trashCreate, { method: "POST", - headers: { "X-CSRFToken": getCsrfToken() }, + headers: { "X-CSRFToken": U.getCsrfToken() }, body: formData, - credentials: "same-origin", + }).then(function () { + trashForm.reset(); + document.getElementById("trash-photo-preview").innerHTML = ""; + U.closeModal("trash-modal"); + U.showToast("Trash report submitted!", "success"); + if (pendingMarker) { map.removeLayer(pendingMarker); pendingMarker = null; } + loadFeatures(); + }).catch(function (err) { + U.showToast(err.message || "Failed to submit report.", "error"); + }).finally(function () { + setLoading("trash-submit-btn", false); + var label = document.querySelector("#trash-submit-btn .btn-label"); + if (label) label.textContent = "Submit Report"; }); + }); - if (pendingRouteLayer) { - map.removeLayer(pendingRouteLayer); - pendingRouteLayer = null; - } - form.reset(); - document.getElementById("route-coordinates").value = ""; - closeModal("route-modal"); - await loadFeatures(); + /* ---- Cleanup mode: mark cleaned ---- */ + function openCleanedModal(siteId) { + if (!requireAuth()) return; + document.getElementById("cleaned-site-id").value = siteId; + U.openModal("cleaned-modal"); } - function enableReportMode() { - reportMode = true; - document.getElementById("report-trash-btn").textContent = "Click Map to Place Pin"; - } + cleanedForm.addEventListener("submit", function (e) { + e.preventDefault(); + if (!requireAuth()) return; + var siteId = document.getElementById("cleaned-site-id").value; + if (!siteId) return; + setLoading("cleaned-submit-btn", true); - function disableReportMode() { - reportMode = false; - document.getElementById("report-trash-btn").textContent = "Report Trash"; - } + var formData = new FormData(cleanedForm); + var url = config.endpoints.trashUpdateBase + siteId + "/mark-cleaned/"; - map.on("click", function (event) { - if (!reportMode) { - return; - } - document.getElementById("trash-lat").value = event.latlng.lat.toFixed(7); - document.getElementById("trash-lng").value = event.latlng.lng.toFixed(7); - openModal("trash-modal"); - disableReportMode(); + U.fetchJson(url, { + method: "POST", + headers: { "X-CSRFToken": U.getCsrfToken() }, + body: formData, + }).then(function () { + cleanedForm.reset(); + document.getElementById("before-photo-preview").innerHTML = ""; + document.getElementById("after-photo-preview").innerHTML = ""; + U.closeModal("cleaned-modal"); + U.showToast("Cleanup proof submitted!", "success"); + loadFeatures(); + showDetail(siteId); + }).catch(function (err) { + U.showToast(err.message || "Failed to submit cleanup.", "error"); + }).finally(function () { + setLoading("cleaned-submit-btn", false); + var label = document.querySelector("#cleaned-submit-btn .btn-label"); + if (label) label.textContent = "Submit Cleanup"; + }); }); - map.on(L.Draw.Event.CREATED, function (event) { - if (event.layerType !== "polyline") { - return; - } - if (pendingRouteLayer) { - map.removeLayer(pendingRouteLayer); + /* ---- Popup event delegation for mark-cleaned ---- */ + map.on("popupopen", function (e) { + var popup = e.popup.getElement(); + if (!popup) return; + var btn = popup.querySelector("[data-mark-cleaned]"); + if (btn) { + btn.addEventListener("click", function () { + openCleanedModal(btn.getAttribute("data-mark-cleaned")); + map.closePopup(); + }); } - pendingRouteLayer = event.layer.addTo(map); - const coords = event.layer.getLatLngs().map((latlng) => [latlng.lng, latlng.lat]); - document.getElementById("route-coordinates").value = JSON.stringify(coords); - openModal("route-modal"); }); - document.querySelectorAll("[data-close-modal]").forEach((button) => { - button.addEventListener("click", function () { - closeModal(button.dataset.closeModal); - disableReportMode(); + /* ---- Filter apply ---- */ + if (applyFiltersBtn) { + applyFiltersBtn.addEventListener("click", function () { + loadFeatures(); }); - }); + } - document.getElementById("apply-filters-btn").addEventListener("click", function () { - loadFeatures().catch((error) => alert(error.message)); - }); - document.getElementById("report-trash-btn").addEventListener("click", function () { - if (reportMode) { - disableReportMode(); - return; - } - enableReportMode(); - }); - document.getElementById("log-route-btn").addEventListener("click", function () { - drawHandler.enable(); - }); + /* ---- Reload on pan/zoom ---- */ + map.on("moveend", loadFeatures); - document.getElementById("trash-form").addEventListener("submit", function (event) { - handleTrashCreate(event).catch((error) => alert(error.message)); - }); - document.getElementById("cleaned-form").addEventListener("submit", function (event) { - handleMarkCleaned(event).catch((error) => alert(error.message)); - }); - document.getElementById("route-form").addEventListener("submit", function (event) { - handleRouteCreate(event).catch((error) => alert(error.message)); - }); + /* ---- Photo previews ---- */ + U.setupPhotoPreview("trash-photos", "trash-photo-preview", 5); + U.setupPhotoPreview("cleaned-before-photos", "before-photo-preview", 5); + U.setupPhotoPreview("cleaned-after-photos", "after-photo-preview", 5); - map.on("moveend zoomend", function () { - loadFeatures().catch((error) => { - setDetailHtml(renderDetailNotice("Map error", error.message)); - }); - }); + /* ---- Init ---- */ + setMode("report"); + loadDistricts(); + loadFeatures(); - ensureOverlayModeSelection(null); - syncCustomCountyListVisibility(); - syncCountyBoundaryVisibility().catch((error) => { - setDetailHtml(renderDetailNotice("Overlay error", error.message)); - }); - loadFeatures() - .then(function () { - return focusRequestedFeature(); - }) - .catch((error) => { - setDetailHtml(renderDetailNotice("Map error", error.message)); - }); + // Deep link to a specific site from URL params + var params = new URLSearchParams(window.location.search); + var focusId = params.get("focus_id"); + if (focusId) { + showDetail(focusId); + } })(); diff --git a/static/js/utils.js b/static/js/utils.js new file mode 100644 index 0000000..a9e9892 --- /dev/null +++ b/static/js/utils.js @@ -0,0 +1,149 @@ +/** + * Shared utility functions for the District 3 CleanUp app. + */ +(function () { + "use strict"; + + function getCsrfToken() { + var meta = document.querySelector('meta[name="csrf-token"]'); + if (meta) return meta.getAttribute("content"); + var match = document.cookie + .split(";") + .map(function (v) { return v.trim(); }) + .find(function (v) { return v.startsWith("csrftoken="); }); + return match ? decodeURIComponent(match.split("=")[1]) : ""; + } + + function escapeHtml(text) { + if (!text) return ""; + var div = document.createElement("div"); + div.appendChild(document.createTextNode(text)); + return div.innerHTML; + } + + /* ---- Toast notifications ---- */ + + function showToast(message, type) { + type = type || "info"; + var container = document.getElementById("toast-container"); + if (!container) return; + var toast = document.createElement("div"); + toast.className = "toast toast--" + type; + toast.setAttribute("role", "status"); + toast.textContent = message; + container.appendChild(toast); + setTimeout(function () { + toast.style.opacity = "0"; + toast.style.transition = "opacity 0.3s ease"; + setTimeout(function () { toast.remove(); }, 300); + }, 4000); + } + + /* ---- Modal focus management ---- */ + + var _focusReturnTarget = null; + var _activeTrapHandler = null; + + function openModal(id) { + var modal = document.getElementById(id); + if (!modal) return; + _focusReturnTarget = document.activeElement; + modal.classList.remove("hidden"); + var focusable = modal.querySelectorAll( + 'a[href], button:not([disabled]), textarea, input:not([type="hidden"]), select, [tabindex]:not([tabindex="-1"])' + ); + if (focusable.length) focusable[0].focus(); + + _activeTrapHandler = function (e) { + if (e.key === "Escape") { + closeModal(id); + return; + } + if (e.key !== "Tab") return; + var first = focusable[0]; + var last = focusable[focusable.length - 1]; + if (e.shiftKey) { + if (document.activeElement === first) { e.preventDefault(); last.focus(); } + } else { + if (document.activeElement === last) { e.preventDefault(); first.focus(); } + } + }; + document.addEventListener("keydown", _activeTrapHandler); + } + + function closeModal(id) { + var modal = document.getElementById(id); + if (!modal) return; + modal.classList.add("hidden"); + if (_activeTrapHandler) { + document.removeEventListener("keydown", _activeTrapHandler); + _activeTrapHandler = null; + } + if (_focusReturnTarget) { + _focusReturnTarget.focus(); + _focusReturnTarget = null; + } + } + + /* ---- Async fetch wrapper ---- */ + + function fetchJson(url, options) { + options = options || {}; + options.credentials = "same-origin"; + return fetch(url, options).then(function (response) { + return response.json().then(function (data) { + if (!response.ok) { + var msg = data.error || "Request failed."; + throw new Error(msg); + } + return data; + }); + }); + } + + /* ---- Photo preview ---- */ + + function setupPhotoPreview(inputId, previewId, maxCount) { + maxCount = maxCount || 5; + var input = document.getElementById(inputId); + var preview = document.getElementById(previewId); + if (!input || !preview) return; + + input.addEventListener("change", function () { + preview.innerHTML = ""; + var files = Array.from(input.files).slice(0, maxCount); + if (input.files.length > maxCount) { + showToast("Maximum " + maxCount + " photos allowed. Only the first " + maxCount + " will be used.", "error"); + var dt = new DataTransfer(); + files.forEach(function (f) { dt.items.add(f); }); + input.files = dt.files; + } + files.forEach(function (file) { + var img = document.createElement("img"); + img.alt = "Upload preview"; + img.src = URL.createObjectURL(file); + preview.appendChild(img); + }); + }); + } + + /* ---- Screen reader announce ---- */ + + function announce(message) { + var el = document.getElementById("mode-instructions"); + if (el) el.textContent = message; + } + + /* ---- Expose ---- */ + + window.AppUtils = { + getCsrfToken: getCsrfToken, + escapeHtml: escapeHtml, + showToast: showToast, + openModal: openModal, + closeModal: closeModal, + fetchJson: fetchJson, + setupPhotoPreview: setupPhotoPreview, + announce: announce, + }; +})(); diff --git a/templates/base.html b/templates/base.html index 380513b..f72d698 100644 --- a/templates/base.html +++ b/templates/base.html @@ -4,7 +4,8 @@ - Upper Cumberland CleanUp Map + + {% block title %}Upper-Cumberland CleanUp{% endblock %} @@ -13,56 +14,60 @@ {% block extra_head %}{% endblock %} +
-
- - Upper Cumberland Region - Upper Cumberland CleanUp - - {% if user.is_authenticated %} - - {% endif %} -
- {% if user.is_authenticated %} + + Upper-Cumberland CleanUp + + +
-
+
{% block content %}{% endblock %}
+
{% if user.is_authenticated %} -