Interactive 3D map application for visualizing projects across Russia, built with Next.js and MapLibre GL JS.
- Interactive 3D Map: Pan, zoom, tilt, and rotate with MapLibre GL JS
- 3D Buildings: Extruded 3D buildings with customizable height and transparency
- Interactive Camera Controls:
- Ctrl + drag to adjust pitch (tilt)
- Right-click + drag to rotate map (bearing)
- 2D/3D toggle button with smooth animations
- Auto-sync button state with interactive pitch changes
- Dual Basemap: Toggle between light (OpenStreetMap) and dark (CartoDB) styles
- Distance Measurement Tool:
- Click to add measurement points
- Real-time distance calculation using Haversine formula
- Shows segment and total distances
- Keyboard shortcuts (Backspace to undo, Esc to clear)
- Interactive Minimap:
- Overview map in bottom-left corner
- Resizable (150-400px) with +/- buttons
- Drag to navigate main map
- Click to jump to location
- Shows viewport rectangle and project markers
- Theme synchronization (dark/light)
- Size persists across all interactions
- 200+ Mock Projects: Realistic dataset across 33+ Russian cities
- Smart Clustering: Automatically groups nearby projects at lower zoom levels
- Site Outlines: Octagonal polygons (1 hectare) around projects
- 3D Buildings: Rectangular footprints (2000 m²) extruded to 17m height
- Adaptive Labels: Independent label control with contrast optimization
- Status/Type Colors: Visual coding by project status and type
- Advanced Filtering: Filter by status, type, dates, and viewport
- Project Search: Search by name or ID with instant results
- Dual-Mode Geocoding:
- Local Mode: Offline search in 40+ Russian cities (MiniSearch)
- External Mode: Global search via Photon API with typo-tolerance
- Viewport Filter: Show only projects visible in current map view
- Virtual List: Efficiently displays large project lists with react-window
- Permalink Support: Share map views via URL (center, zoom, filters, selection)
- Floating Legend: Semi-transparent widget with configurable opacity
- Language Switcher: Toggle between English and Russian (saved in cookie)
- Responsive Layout: Sidebar with filters, legend, and project list
- Interactive Selection: Synchronized highlighting between map and list
- Framework: Next.js 15 (App Router)
- Language: TypeScript
- Styling: Tailwind CSS
- Mapping: MapLibre GL JS (with 3D support)
- Search: MiniSearch (local geocoding)
- Virtualization: react-window
- I18n: next-intl
- Code Quality: Biome (linting & formatting)
- Node.js 18+ and npm
# Install dependencies
npm install
# Prepare geocoder index (generates mock Russian cities data)
npm run prepare:geocoder
# Run development server
npm run devOpen http://localhost:3000 (redirects to /map)
# Build the application
npm run build
# Start production server
npm startprojects-map/
├── app/
│ ├── actions.ts # Server actions (language switching)
│ ├── contexts/ # React contexts (MapContext)
│ ├── hooks/ # Custom hooks (useMapProjects, useSearchMapProjects, etc.)
│ ├── map/ # Main map page
│ └── layout.tsx # Root layout with i18n
├── components/
│ ├── map/
│ │ └── hooks/
│ │ ├── useMinimap.ts # Minimap control hook
│ │ ├── useMapInteraction.ts
│ │ ├── useMapLayers.ts
│ │ └── ... # Other map hooks
│ ├── MapView.tsx # Main map component with 3D support
│ ├── FloatingLegend.tsx # Floating legend widget
│ ├── LocaleSwitcher.tsx # Language switcher
│ ├── LayerToggles.tsx # Layer visibility controls
│ ├── SearchBar.tsx # Project/address search
│ ├── FiltersPanel.tsx # Filtering controls
│ └── ... # Other UI components
├── lib/
│ ├── config.ts # App configuration
│ ├── styleConfig.ts # Map styling configuration
│ ├── types.ts # TypeScript type definitions
│ ├── mockData.ts # Mock project generator (200 projects)
│ ├── buildingGeometry.ts # 3D geometry coordinate transformations
│ ├── buildingGenerators.ts # Complex building generation with collision detection
│ ├── measurementUtils.ts # Distance measurement utilities
│ ├── localGeocoder.ts # MiniSearch-based local geocoding
│ └── useSmartTranslations.ts # Type-safe i18n hook
├── i18n/
│ ├── messages/ # Translation files (en.json, ru.json)
│ └── request.ts # next-intl configuration
├── scripts/
│ └── prepare-geocoder.ts # Script to generate geocoder index
└── public/
└── data/
└── geocoder-index.json # Pre-generated geocoder data
# Development
npm run dev # Start dev server with Turbopack
# Build
npm run build # Build for production
npm run start # Start production server
# Data Preparation
npm run prepare:geocoder # Generate geocoder index
# Code Quality
npm run lint # Check code with Biome
npm run lint:fix # Auto-fix code issues
npm run format # Format codeEdit lib/config.ts to customize:
- Map basemaps (light/dark tile sources)
- Initial center and zoom
- Total mock projects count
- Geocoder settings (local/external mode)
- 3D buildings settings (min zoom, height, footprint area)
- UI settings (legend opacity)
Edit lib/styleConfig.ts to customize:
- Status colors (planning, inProgress, completed, etc.)
- Project type colors
- Cluster colors
- MapLibre style expressions
- Complex 3D Buildings: Each project features a unique multi-building complex (3-6 buildings)
- Randomized Architecture: Buildings are procedurally generated with type-specific characteristics:
- Housing: 8-14 × 18-30 m, heights 15-30 m (residential blocks)
- Commercial: 10-20 × 12-25 m, heights 10-25 m (retail/office)
- Infrastructure: 12-24 × 12-26 m, heights 12-28 m (utilities)
- Social: 12-24 × 12-24 m, heights 20-40 m (schools, hospitals)
- Transport: 14-28 × 10-18 m, heights 8-18 m (stations, terminals)
- Smart Constraints:
- All buildings contained within octagonal site boundaries (1 hectare)
- No overlapping buildings (2 meter minimum gaps)
- All buildings axis-aligned (90-degree angles only)
- Height variation (minimum 15% difference between buildings)
- Deterministic Generation: Buildings generated from project ID seed for consistency
- Instant Switching: Zero-delay transition between points and 3D buildings
- Independent Controls: Separate toggles for points, buildings, and labels
- Pitch Control: Tilt map from 0° (top-down) to 60° (perspective)
- Bearing Control: Rotate map to any angle
- Smooth Animations: 1-second transitions for programmatic changes
- Auto-sync UI: Button state automatically reflects current view mode
- Haversine Formula: Accurate distance calculation on sphere
- Visual Feedback: Line and point markers for measurement path
- Distance Format: Meters (< 1km) or kilometers (≥ 1km)
- Keyboard Shortcuts:
- Backspace: Remove last point
- Esc: Clear all measurements
- Custom IControl: Native MapLibre control implementation
- Dynamic Sizing: Resize between 150-400px using +/- buttons
- Drag Navigation: Click and drag to move main map instantly
- Click to Jump: Single click to fly to any location
- Viewport Visualization: Semi-transparent blue rectangle shows current view
- Project Markers: Red dots indicate all project locations
- Theme Sync: Automatically switches between dark/light basemaps
- Size Persistence: Maintains size across theme changes and map interactions
- Smart Updates: Updates theme and data without recreating control
- Clustering: Automatically groups nearby projects at lower zoom levels
- Hover Effects: Highlights projects using MapLibre feature-state
- Click Actions: Opens project details in popup modal
- Cluster Expansion: Click clusters to zoom to bounds
- Status: Planning, In Progress, Completed, On Hold, Cancelled
- Type: Infrastructure, Housing, Commercial, Industrial, Social, Transport
- Date Range: Filter by start and end dates
- Viewport Only: Show only projects visible in current map view
- Projects: Search by name or ID with instant results
- Addresses (Local Mode):
- Offline search in 40+ major Russian cities
- MiniSearch-powered fuzzy search
- Supports transliteration (ru↔lat)
- Addresses (External Mode):
- Global search via Photon API
- Search-as-you-type with debounce
- Typo-tolerant
- Mock Data: 200 deterministic projects across Russia
- 50% Polygons: Half of projects include boundary polygons
- Realistic Distribution: Projects centered around 33 major Russian cities
- Complex Building Geometry: Each project has unique 3D building complex (3-6 buildings) generated at creation time
- Dynamic Site Outlines: 1 hectare octagonal boundaries generated on-the-fly
The application supports two languages with cookie-based persistence:
- English (default)
- Russian
Language detection order:
- NEXT_LOCALE cookie (set via language switcher)
- Browser's Accept-Language header
- Default: English
Switch languages using the EN/RU button in the page header. Selection is saved to cookie and persists across sessions.
The application includes 200 mock projects with:
- Locations: Distributed across 33 major Russian cities
- Statuses: Planning, In Progress, Completed, On Hold, Cancelled
- Types: Infrastructure, Housing, Commercial, Industrial, Social, Transport
- Budgets: Range from 1M to 500M RUB
- Dates: Project timelines from 2020-2024
Note: Mock data is deterministic (seeded) for consistency. Change the
SEEDconstant inlib/mockData.tsto generate a different dataset.
- Differential Updates: Only changed GeoJSON sources are updated
- Feature State: Hover highlighting without GeoJSON rebuild
- Virtual List: Efficient rendering of large project lists
- Debounced Loading: 300ms debounce for viewport-based data loading
- Request Cancellation: Outdated requests cancelled automatically
- Zero-Delay 3D: Direct event handlers with refs for instant 3D switching
- 3D Switching Without Delays: Use direct map event handlers with refs instead of useEffect to avoid React render delays
- Infinite Loop Prevention: Use programmatic change flags (refs) when syncing UI state with map events
- Dark Mode Colors: Use pure Tailwind classes for semi-transparent backgrounds, NOT inline styles with opacity
- Type-Safe i18n: useSmartTranslations provides autocomplete for all translation keys
- Cookie-based Locale: Language preference stored in cookie, NOT in URL routing
- Minimap Size Persistence: Use refs to store state across control recreation, separate useEffect for initial setup vs updates
- Drag & Drop Without Glitches: Block sync during drag, use jumpTo for instant movement, add global mouseup handler
The project uses Biome for code quality. Key rules:
- No non-null assertions (use local variables instead)
- No array index keys (use compound keys)
- Exhaustive dependencies (don't add unnecessary deps)
- SVG accessibility (title elements required)
Run npm run lint to check and npm run lint:fix to auto-fix issues.
MIT
- Map tiles: © OpenStreetMap contributors
- Dark basemap: CartoDB Dark Matter
- Geocoding data: GeoNames (cities1000, Russia)
- Distance calculations: Haversine formula