anvaka/city-roads
Visualization of all roads within any city
Solo project — review before adopting
single-maintainer (no co-maintainers visible); no tests detected…
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
Scorecard "Branch-Protection" is 0/10; no CI workflows detected
- ⚠Solo or near-solo (1 contributor active in recent commits)
- ⚠No CI workflows detected
- ⚠No test directory detected
- ⚠Scorecard: default branch unprotected (0/10)
- ✓Last commit 8w ago
- ✓MIT licensed
What would improve this?
- →Use as dependency Mixed → Healthy if: onboard a second core maintainer; add a test suite
- →Deploy as-is Mixed → Healthy if: bring "Branch-Protection" to ≥3/10 (see scorecard report)
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests + OpenSSF Scorecard
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Forkable" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/anvaka/city-roads)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card
This card auto-renders when someone shares https://repopilot.app/r/anvaka/city-roads on X, Slack, or LinkedIn.
Ask AI about anvaka/city-roads
Grounded in the actual source code. Pick a starter question or write your own.
Onboarding doc
Onboarding: anvaka/city-roads
Generated by RepoPilot · 2026-06-21 · Source
🎯Verdict
WAIT — Solo project — review before adopting
- Last commit 8w ago
- MIT licensed
- ⚠ Solo or near-solo (1 contributor active in recent commits)
- ⚠ No CI workflows detected
- ⚠ No test directory detected
- ⚠ Scorecard: default branch unprotected (0/10)
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests + OpenSSF Scorecard</sub>
⚡TL;DR
city-roads renders every road in any city by fetching OpenStreetMap data via the Overpass API and visualizing it with WebGL. It pre-indexes ~3,000 cities (pop. >100k) as protobuf files to avoid rate-limiting, then uses w-gl (a WebGL wrapper) to render thousands of road segments interactively in the browser. Single-page Vue 3 app (src/App.vue) with Vite. Core logic splits into: src/lib/ (scene creation, grid/bounding box logic, file export), src/components/ (UI: ColorPicker, FindPlace, LoadingIcon), src/proto/ (protobuf encode/decode for city cache), and src/createScene.js / src/lib/createScene.js (WebGL scene setup with w-gl).
👥Who it's for
Cartography enthusiasts, data visualization artists, and developers building on top of OSM data who want pre-cached city geometries and a browser-based rendering engine. Also urban planners and designers exploring road network aesthetics.
🌱Maturity & risk
Actively maintained single-author project (Andrei Kashcha) with clean Vue 3 / Vite setup and no visible test infrastructure. The codebase is production-ready for viewing (runs on phones) but lacks automated tests. Recent activity visible through ongoing bug fixes and feature requests.
Single maintainer with no CI/CD pipeline (.github/FUNDING.yml suggests sponsorship model, not team). Small dependency surface (11 dependencies, mostly stable: vue@3.2, w-gl@0.21, pbf@3.2) but tight coupling to Overpass API rate limits and Nominatim. Browser memory constraints are hard limit for large cities (Tokyo crashes on some devices). No tests mean refactoring risk is high.
Active areas of work
No explicit commit/PR data in file listing, but the repo structure shows active development: custom vue3-color picker bundled, protobuf caching system in place, and scripting API (API.md) suggesting extensibility. Sponsorship model indicates ongoing maintenance.
🚀Get running
git clone https://github.com/anvaka/city-roads.git && cd city-roads && npm install && npm run dev — opens http://localhost:5173 with Vite hot reload.
Daily commands: npm run dev (Vite dev server) → npm run build (Vite production bundle) → npm run lint (ESLint fix). Deploy via deploy.sh (shell script in root).
🗺️Map of the codebase
src/main.js— Vue 3 application entry point; initializes the app and mounts root componentsrc/App.vue— Root Vue component managing overall UI layout, search, and scene orchestrationsrc/lib/createScene.js— Core graphics engine initialization using w-gl for WebGL rendering of road networkssrc/lib/Query.js— Orchestrates road data fetching from Overpass API or cached protobuf indexsrc/lib/appState.js— Centralized application state management for city selection, viewport, and UI statesrc/proto/place.proto— Protocol buffer schema for pre-indexed city boundaries and metadatavite.config.js— Build configuration defining asset optimization and Vue plugin setup
🛠️How to make changes
Add a new UI control (e.g., brightness slider)
- Create a new .vue component in src/components/ following the pattern of ColorPicker.vue (
src/components/MyControl.vue) - Import and add the component to the template section of App.vue (
src/App.vue) - Emit events via appState or bus to communicate changes to the scene renderer (
src/lib/appState.js) - Handle the event in createScene.js to update road rendering properties (
src/lib/createScene.js)
Add export format (e.g., GeoJSON)
- Create src/lib/geojsonExport.js following the pattern of svgExport.js (
src/lib/geojsonExport.js) - Add export button to App.vue that triggers the export function (
src/App.vue) - Extract road data from GridLayer using existing grid traversal logic (
src/lib/GridLayer.js)
Support a new data source beyond Overpass API
- Add conditional logic in src/lib/Query.js to switch data source based on configuration (
src/lib/Query.js) - Create a new adapter function in src/lib/request.js to fetch from the new source (
src/lib/request.js) - Normalize response data to the existing road geometry format consumed by GridLayer (
src/lib/GridLayer.js) - Update src/config.js with new API endpoint and feature flags (
src/config.js)
Improve search with autocomplete suggestions
- Enhance FindPlace.vue to show dropdown list during typing (
src/components/FindPlace.vue) - Call Nominatim API with limit parameter in request.js (
src/lib/request.js) - Store recent searches in appState for quick access (
src/lib/appState.js)
🔧Why these technologies
- Vue 3 — Reactive UI framework for responsive search, controls, and state synchronization across components
- w-gl (WebGL wrapper) — High-performance graphics rendering for visualizing thousands of road segments without lag
- Overpass API — Free, community-maintained source of OpenStreetMap road data; supports complex geographic queries
- Protobuf (pbf) — Compact binary format for pre-cached city boundaries (~3,000 cities); dramatically reduces download time vs JSON
- Nominatim — Free reverse-geocoding service to resolve user-typed city names to Overpass area IDs
- Vite — Modern bundler with fast HMR and optimized production builds for web visualization
⚖️Trade-offs already made
-
Pre-cache only ~3,000 large cities (>100k population)
- Why: Reduces storage and maintenance burden; covers 90% of user queries
- Consequence: Uncached cities require slower Overpass fallback; user may experience 5-30s wait time
-
Render all roads in single WebGL pass rather than streaming tiles
- Why: Simpler UX and code; eliminates tile stitching artifacts
- Consequence: Cannot support extremely large cities; memory/draw call limits around 100k+ road segments
-
Store state in URL query string (LoadOptions.js)
- Why: Shareable links preserve viewport, colors, and city selection without backend
- Consequence: URL can become long; state size limited to browser URL length (~2KB)
-
Client-side export (SVG, PNG) rather than server-side rendering
- Why: No backend infrastructure required; instant export without latency
- Consequence: Large cities may timeout or crash in browser; limited to export resolution of canvas
🚫Non-goals (don't propose these)
- Real-time road data updates (data is snapshot of OpenStreetMap at cache time)
- Mobile-optimized UI (responsive but not touch-gesture optimized)
- Authentication or user accounts (entirely anonymous and stateless)
- Offline functionality (requires API access for uncached cities)
- 3D visualization (2D road network only)
- Routing or navigation (view-only visualization)
🪤Traps & gotchas
- Overpass API is rate-limited and may reject large bounding boxes; the protobuf cache at anvaka/index-large-cities is required for reliable city queries. 2. Browser memory is the hard limit: rendering >1.4M segments (Tokyo) will freeze/crash even on fast devices. 3. w-gl requires WebGL2 or WebGL1 with ANGLE on Windows; see src/NoWebGL.vue for fallback UI. 4. Nominatim nominatim.openstreetmap.org has its own rate limits; large batch searches should be staggered. 5. No environment variables documented, but the cache URL is hardcoded — forking requires updating the city-cache URL in src/lib/Query.js.
🏗️Architecture
💡Concepts to learn
- Protocol Buffers (protobuf) — City-roads uses protobuf to compress and cache ~3,000 city geometries in index-large-cities, reducing API calls; understanding src/proto/decode.js is critical for optimizing or extending the cache.
- Overpass API query language — Roads are fetched via Overpass QL queries in src/lib/Query.js; knowing bbox queries and way/node filtering is essential for debugging or expanding road filtering logic.
- Mercator projection (d3-geo) — src/lib/createScene.js uses d3-geo to project lat/lon to 2D canvas coordinates; understanding Mercator distortion is needed for correct pan/zoom and export accuracy.
- WebGL line rendering (w-gl) — src/lib/GridLayer.js batches road segments for efficient WebGL rendering; understanding w-gl's line API and batch updates is critical for performance tuning or adding new geometry types.
- Query state binding (URL serialization) — src/lib/appState.js uses query-state to sync zoom, bounds, and color to URL parameters; this enables shareable city-road links but requires careful state management.
- Rate limiting and caching strategy — The entire architecture (protobuf cache + fallback to Overpass) exists to mitigate Overpass rate limits; understanding why large bounding boxes require pre-cached indices is critical for scaling.
- Canvas/SVG export pipelines — src/lib/svgExport.js and src/lib/saveFile.js convert rendered WebGL scenes to vector/raster formats; understanding these export paths is needed for print or design integration features.
🔗Related repos
anvaka/index-large-cities— Companion repo containing the pre-indexed ~3,000 cities in protobuf format that city-roads queries to avoid Overpass rate limitsanvaka/city-script— Examples and utilities for scripting on top of city-roads scenes, using the Scene API documented in API.mdanvaka/w-gl— The WebGL rendering library (v0.21.0) that powers road visualization; essential dependency for understanding canvas/shader layeropenstreetmap/overpass-turbo— Interactive frontend for Overpass API that city-roads queries behind the scenes; useful for debugging road data requestsOSM-de/osmium-tool— Alternative high-performance tool for processing OSM extracts; relevant for users wanting to batch-process road data outside the browser
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add unit tests for src/lib/Query.js and src/lib/BoundingBox.js
These core spatial/query modules have no corresponding test files. Query.js handles the Overpass API interactions and BoundingBox.js manages coordinate calculations - both are critical for correctness. Adding tests would catch regressions when refactoring the data fetching pipeline and ensure coordinate transformations work across edge cases (date line, poles, etc.).
- [ ] Create src/lib/tests/Query.test.js with tests for API request formatting and response handling
- [ ] Create src/lib/tests/BoundingBox.test.js with tests for coordinate validation, bounds intersection, and edge cases
- [ ] Add test script to package.json (e.g., 'vitest' or 'jest')
- [ ] Add test dependency to package.json (vitest recommended for Vite projects)
- [ ] Verify tests run in CI by adding GitHub Action workflow
Add GitHub Actions workflow for automated builds and artifact storage
The repo has a deploy.sh script but no CI/CD automation. The .github/FUNDING.yml exists but no workflows. Adding automated builds on push/PR would catch build errors early, ensure the Vite bundle always works, and could auto-deploy to GitHub Pages. Given the visualization-heavy nature and dependencies like w-gl and stylus, build failures matter.
- [ ] Create .github/workflows/build.yml that runs 'npm install' and 'npm run build' on push and PR
- [ ] Add build output validation (check that dist/ contains index.html and expected assets)
- [ ] Create .github/workflows/deploy.yml that runs deploy.sh on tags or main branch merges
- [ ] Add npm lint check to build workflow to catch style issues
- [ ] Document workflow setup in CONTRIBUTING.md (create if missing)
Extract and document the protobuf pipeline in src/proto/
The protobuf encoding/decoding in src/proto/encode.js, src/proto/decode.js, and src/proto/place.proto are critical to the city indexing system, but there's no documentation explaining the data structure or how to regenerate indices. The README snippet mentions the external index-large-cities repo but doesn't explain how to use/update the local proto files. Add detailed API docs and a generation script.
- [ ] Create PROTOBUF_GUIDE.md documenting place.proto schema, encode/decode behavior, and why each field exists
- [ ] Add comments to src/proto/encode.js and src/proto/decode.js explaining the binary format and field mapping
- [ ] Create a script docs/regenerate-proto.md explaining how contributors can rebuild proto definitions if pbf dependency changes
- [ ] Add usage example in API.md showing how to encode/decode a city Place object
- [ ] Reference the external index-large-cities repo workflow for maintaining cached cities
🌿Good first issues
- Add unit tests for src/lib/BoundingBox.js: BoundingBox.js has no tests; adding Jest tests for coordinate normalization and bounds checking would catch regressions in map pan/zoom logic.
- Document scripting API with examples in src/examples/ directory: API.md exists but src/ lacks runnable example scripts; adding 2-3 example .vue or .js files (e.g. highlighting grid cells, filtering by road type) would help users leverage the scene API documented in API.md.
- Add error handling UI for Nominatim timeouts in src/components/FindPlace.vue: Currently FindPlace.vue silently fails if nominatim.openstreetmap.org is slow; adding a retry button and timeout warning would improve UX during high-load periods.
📝Recent commits
Click to expand
Recent commits
0d89417— npm audit fix (anvaka)af01ea2— Improve error handling for OpenStreetMap data retrieval (anvaka)88557e3— updated year (anvaka)14f1ea1— Move to cloudfront (anvaka)f30b2ba— npm audit fix (anvaka)c7329e2— Updated years (anvaka)8ff98b5— npm audit fix (anvaka)ff6f824— npm audit fix (anvaka)e7f5b47— npm audit fix (anvaka)5f2736b— npm audit fix (anvaka)
🔒Security observations
- Medium · Outdated Vue.js Dependency —
package.json - vue dependency. Vue.js 3.2.37 is over 2 years old and contains known vulnerabilities. The latest stable version should be used to receive security patches and bug fixes. Fix: Update vue to the latest stable version (^3.4.x or higher). Run 'npm update vue' and test thoroughly before deployment. - Medium · Outdated Vite Build Tool —
package.json - vite devDependency. Vite 2.9.5 is significantly outdated (current is 5.x+). Older versions may contain security vulnerabilities and lack important security features present in newer releases. Fix: Upgrade to Vite 5.x or latest stable version. Review breaking changes and update configuration accordingly in vite.config.js. - Medium · Outdated ESLint and Related Tools —
package.json - eslint devDependencies. ESLint 8.5.0 and eslint-plugin-vue 8.2.0 are outdated and may not catch modern security issues or best practices. Current versions are 9.x+ for ESLint. Fix: Update ESLint to latest stable version (9.x) and corresponding eslint-plugin-vue. Run 'npm update eslint eslint-plugin-vue'. - Medium · External API Dependency - Overpass API —
src/lib/Query.js, src/lib/request.js, README.md. The application relies on external APIs (Overpass API, Nominatim) without apparent rate limiting or caching mechanisms beyond protobuf storage. This could expose users to DoS conditions or service unavailability. Fix: Implement client-side rate limiting, add request timeouts, cache responses client-side, and display appropriate error messages when APIs are unavailable or rate-limited. - Medium · Potential XSS via Search/Place Names —
src/components/FindPlace.vue, src/lib/findBoundaryByName.js. The application accepts user input via search box (FindPlace.vue) and displays city names. Without proper sanitization, this could allow XSS attacks if the data from Nominatim or other sources is not properly escaped. Fix: Ensure all user input and external API responses are properly escaped using Vue's built-in v-text or text interpolation instead of v-html. Validate and sanitize data from external APIs. - Low · Missing Content Security Policy (CSP) —
index.html, deploy.sh, vite.config.js. No visible CSP headers configured in the application or deployment files. This increases XSS vulnerability surface. Fix: Implement a strict Content Security Policy header in the deployment configuration and HTML meta tags. Example: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'. - Low · No HTTPS Enforcement Visible —
deploy.sh. The deploy.sh script and configuration do not show explicit HTTPS enforcement or security headers configuration. Fix: Ensure HTTPS is enforced, add security headers (Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options, etc.) in the deployment configuration. - Low · Unrestricted External Resource Loading —
src/lib/request.js, package.json - d3-require dependency. The application loads data from external sources (OpenStreetMap, Nominatim) without apparent validation. The d3-require dependency dynamically loads external modules. Fix: Validate all external data against a schema before processing. Use CORS properly, implement request signing if possible, and consider using Subresource Integrity (SRI) for any external resources. - Low · Missing Input Validation Framework —
src/components/EditableLabel.vue, src/components/FindPlace.vue. No visible validation library (like joi, zod, or vuelidate) for user input validation. The EditableLabel component allows direct input without apparent sanitization. Fix: Implement a validation library for all user inputs. Add length limits,
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/anvaka/city-roads shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live anvaka/city-roads
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/anvaka/city-roads.
What it runs against: a local clone of anvaka/city-roads — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in anvaka/city-roads | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch main exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 86 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of anvaka/city-roads. If you don't
# have one yet, run these first:
#
# git clone https://github.com/anvaka/city-roads.git
# cd city-roads
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of anvaka/city-roads and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "anvaka/city-roads(\\.git)?\\b" \\
&& ok "origin remote is anvaka/city-roads" \\
|| miss "origin remote is not anvaka/city-roads (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
&& ok "default branch main exists" \\
|| miss "default branch main no longer exists"
# 4. Critical files exist
test -f "src/main.js" \\
&& ok "src/main.js" \\
|| miss "missing critical file: src/main.js"
test -f "src/App.vue" \\
&& ok "src/App.vue" \\
|| miss "missing critical file: src/App.vue"
test -f "src/lib/createScene.js" \\
&& ok "src/lib/createScene.js" \\
|| miss "missing critical file: src/lib/createScene.js"
test -f "src/lib/Query.js" \\
&& ok "src/lib/Query.js" \\
|| miss "missing critical file: src/lib/Query.js"
test -f "src/lib/appState.js" \\
&& ok "src/lib/appState.js" \\
|| miss "missing critical file: src/lib/appState.js"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 86 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~56d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/anvaka/city-roads"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.
Similar JavaScript repos
Other mixed-signal JavaScript repos by stars.
Embed this chat in your README →
Drop this iframe anywhere — the widget runs against the same live analysis cache as the main app.
<iframe src="https://repopilot.app/embed/anvaka/city-roads" width="100%" height="500" style="border:1px solid #d0d7de; border-radius:8px;" allow="microphone" loading="lazy" ></iframe>