diff --git a/apps/.DS_Store b/apps/.DS_Store
index e8d45b7..d4f10df 100644
Binary files a/apps/.DS_Store and b/apps/.DS_Store differ
diff --git a/apps/api/package-lock.json b/apps/api/package-lock.json
index 59d37d8..e494f50 100644
--- a/apps/api/package-lock.json
+++ b/apps/api/package-lock.json
@@ -14,6 +14,7 @@
"@nestjs/core": "^11.0.1",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/schedule": "^6.1.1",
+ "@nestjs/serve-static": "^5.0.1",
"@nestjs/swagger": "^11.2.6",
"@nestjs/typeorm": "^11.0.0",
"axios": "^1.13.6",
@@ -2364,6 +2365,41 @@
"tslib": "^2.1.0"
}
},
+ "node_modules/@nestjs/serve-static": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/@nestjs/serve-static/-/serve-static-5.0.5.tgz",
+ "integrity": "sha512-AhYx3N9aMwR2cb0w5Nlb5nHNYiAcF74ea/D/xna+PxlXwjmwGN/PpC/5fuMtOwmPBMgOTxNPOnB8C9LDZBSgyw==",
+ "dependencies": {
+ "path-to-regexp": "8.4.2"
+ },
+ "peerDependencies": {
+ "@fastify/static": "^8.0.4 || ^9.0.0",
+ "@nestjs/common": "^11.0.2",
+ "@nestjs/core": "^11.0.2",
+ "express": "^5.0.1",
+ "fastify": "^5.2.1"
+ },
+ "peerDependenciesMeta": {
+ "@fastify/static": {
+ "optional": true
+ },
+ "express": {
+ "optional": true
+ },
+ "fastify": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@nestjs/serve-static/node_modules/path-to-regexp": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/@nestjs/swagger": {
"version": "11.2.6",
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.6.tgz",
diff --git a/apps/api/package.json b/apps/api/package.json
index 15e89e6..9e4f399 100644
--- a/apps/api/package.json
+++ b/apps/api/package.json
@@ -25,6 +25,7 @@
"@nestjs/core": "^11.0.1",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/schedule": "^6.1.1",
+ "@nestjs/serve-static": "^5.0.1",
"@nestjs/swagger": "^11.2.6",
"@nestjs/typeorm": "^11.0.0",
"axios": "^1.13.6",
diff --git a/apps/api/public/intel/app.js b/apps/api/public/intel/app.js
new file mode 100644
index 0000000..9abfba2
--- /dev/null
+++ b/apps/api/public/intel/app.js
@@ -0,0 +1,156 @@
+// Intaleq Intelligence Dashboard Control
+// المتحكم البرمجي في لوحة ذكاء الخرائط
+
+const API_BASE = '/api';
+
+// Initialize
+document.addEventListener('DOMContentLoaded', () => {
+ fetchSummary();
+ fetchCandidates();
+ fetchClosures();
+
+ // Event Listeners
+ document.getElementById('run-analysis-btn').addEventListener('click', runAnalysis);
+ document.getElementById('discover-closures-btn').addEventListener('click', discoverClosures);
+});
+
+// --- API FETCHERS ---
+
+async function fetchSummary() {
+ try {
+ const res = await fetch(`${API_BASE}/map-refinement/summary`);
+ const data = await res.json();
+
+ document.getElementById('stat-telemetry').innerText = Number(data.telemetry.total).toLocaleString();
+ document.getElementById('stat-roads').innerText = Number(data.roads.analyzed).toLocaleString();
+ document.getElementById('stat-pending').innerText = Number(data.candidates.pending).toLocaleString();
+ document.getElementById('stat-closed').innerText = Number(data.roads.closed || 0).toLocaleString();
+ } catch (e) {
+ showToast('Failed to fetch summary');
+ }
+}
+
+async function fetchCandidates() {
+ const tableBody = document.querySelector('#candidates-table tbody');
+ try {
+ const res = await fetch(`${API_BASE}/map-refinement/candidates?status=pending`);
+ const data = await res.json();
+
+ if (!data || data.length === 0) {
+ tableBody.innerHTML = '
| No pending discoveries 🏜️ |
';
+ return;
+ }
+
+ tableBody.innerHTML = data.map(c => `
+
+ | ${c.id.substring(0,8)}... |
+ ${c.uniqueDriverCount} |
+ ${c.totalPoints} |
+ ${Math.round(c.lengthMeters)}m |
+ ${Math.round(c.confidence * 100)}% |
+
+
+
+ |
+
+ `).join('');
+ } catch (e) {
+ tableBody.innerHTML = '| Error loading candidates |
';
+ }
+}
+
+async function fetchClosures() {
+ const tableBody = document.querySelector('#closures-table tbody');
+ try {
+ const res = await fetch(`${API_BASE}/map-refinement/closures`);
+ const data = await res.json();
+
+ if (!data || data.length === 0) {
+ tableBody.innerHTML = '| No segments flagged |
';
+ return;
+ }
+
+ tableBody.innerHTML = data.map(s => `
+
+ | ${s.segmentId} |
+ ${s.sampleCount} pts |
+ CLOSED |
+
+ `).join('');
+ } catch (e) {
+ tableBody.innerHTML = '| Error loading closures |
';
+ }
+}
+
+// --- ACTIONS ---
+
+async function approveCandidate(id) {
+ if (!confirm('Add this path to the approved map queue?')) return;
+ try {
+ await fetch(`${API_BASE}/map-refinement/candidates/${id}/approve`, { method: 'PATCH' });
+ showToast('Road Approved ✅');
+ fetchCandidates();
+ fetchSummary();
+ } catch (e) {
+ showToast('Approval failed');
+ }
+}
+
+async function rejectCandidate(id) {
+ try {
+ await fetch(`${API_BASE}/map-refinement/candidates/${id}/reject`, { method: 'PATCH' });
+ showToast('Discovery Rejected ❌');
+ fetchCandidates();
+ fetchSummary();
+ } catch (e) {
+ showToast('Rejection failed');
+ }
+}
+
+async function runAnalysis() {
+ const btn = document.getElementById('run-analysis-btn');
+ btn.disabled = true;
+ btn.innerHTML = ' Analyzing...';
+
+ try {
+ await fetch(`${API_BASE}/telemetry/process-intelligence?days=2`, { method: 'POST' });
+ showToast('Deep Intelligence started 🧠. Check Telegram for report soon!');
+ } catch (e) {
+ showToast('Analysis trigger failed');
+ } finally {
+ setTimeout(() => {
+ btn.disabled = false;
+ btn.innerHTML = ' Run Intelligence';
+ fetchSummary();
+ }, 3000);
+ }
+}
+
+async function discoverClosures() {
+ showToast('Scanning for closures... 🚧');
+ try {
+ await fetch(`${API_BASE}/map-refinement/discover-closures`, { method: 'POST' });
+ showToast('Closure scan complete');
+ fetchClosures();
+ fetchSummary();
+ } catch (e) {
+ showToast('Closure discovery failed');
+ }
+}
+
+// --- HELPERS ---
+
+function getConfClass(conf) {
+ if (conf > 0.8) return 'high';
+ if (conf > 0.5) return 'med';
+ return 'low';
+}
+
+function showToast(msg) {
+ const container = document.getElementById('toast-container');
+ const toast = document.createElement('div');
+ toast.className = 'toast';
+ toast.innerText = msg;
+ container.appendChild(toast);
+ setTimeout(() => toast.remove(), 4000);
+}
diff --git a/apps/api/public/intel/index.html b/apps/api/public/intel/index.html
new file mode 100644
index 0000000..ca6a605
--- /dev/null
+++ b/apps/api/public/intel/index.html
@@ -0,0 +1,149 @@
+
+
+
+
+
+ Intaleq | Map Intelligence Dashboard
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/api/public/intel/styles.css b/apps/api/public/intel/styles.css
new file mode 100644
index 0000000..0653a93
--- /dev/null
+++ b/apps/api/public/intel/styles.css
@@ -0,0 +1,365 @@
+:root {
+ --bg-dark: #0f172a;
+ --bg-navbar: #1e293b;
+ --card-bg: rgba(30, 41, 59, 0.7);
+ --border-color: #334155;
+ --text-primary: #f8fafc;
+ --text-secondary: #94a3b8;
+ --accent-purple: #8b5cf6;
+ --accent-blue: #3b82f6;
+ --accent-orange: #f59e0b;
+ --accent-red: #ef4444;
+ --accent-green: #10b981;
+ --sidebar-width: 260px;
+ --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
+ background-color: var(--bg-dark);
+ color: var(--text-primary);
+ overflow: hidden;
+ line-height: 1.5;
+}
+
+.app-container {
+ display: flex;
+ height: 100vh;
+}
+
+/* Sidebar Styling */
+.sidebar {
+ width: var(--sidebar-width);
+ background-color: var(--bg-navbar);
+ border-right: 1px solid var(--border-color);
+ display: flex;
+ flex-direction: column;
+ padding: 1.5rem;
+ z-index: 10;
+}
+
+.logo-container {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ padding-bottom: 2rem;
+ margin-bottom: 1rem;
+ border-bottom: 1px solid var(--border-color);
+}
+
+.logo-icon {
+ width: 40px;
+ height: 40px;
+ background: linear-gradient(135deg, var(--accent-purple), var(--accent-blue));
+ border-radius: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: white;
+ font-size: 1.25rem;
+ box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
+}
+
+.logo-text {
+ font-weight: 700;
+ font-size: 1.25rem;
+ letter-spacing: -0.5px;
+}
+
+.logo-text span {
+ display: block;
+ font-size: 0.75rem;
+ color: var(--text-secondary);
+ font-weight: 400;
+ margin-top: -2px;
+}
+
+.main-nav {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.nav-item {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ padding: 0.75rem 1rem;
+ border-radius: 8px;
+ color: var(--text-secondary);
+ text-decoration: none;
+ font-weight: 500;
+ transition: var(--transition);
+}
+
+.nav-item i {
+ width: 20px;
+ text-align: center;
+}
+
+.nav-item:hover, .nav-item.active {
+ background-color: rgba(255, 255, 255, 0.05);
+ color: white;
+}
+
+.nav-item.active {
+ background-color: rgba(139, 92, 246, 0.1);
+ color: var(--accent-purple);
+}
+
+.status-indicator {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 1rem;
+ background: rgba(0, 0, 0, 0.2);
+ border-radius: 12px;
+ font-size: 0.85rem;
+}
+
+.dot {
+ width: 8px;
+ height: 8px;
+ background-color: var(--accent-green);
+ border-radius: 50%;
+}
+
+.pulse {
+ box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7);
+ animation: pulse 1.5s infinite;
+}
+
+@keyframes pulse {
+ 0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7); }
+ 70% { box-shadow: 0 0 0 8px rgba(16, 185, 129, 0); }
+ 100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
+}
+
+/* Main Content Area */
+.main-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ background-color: var(--bg-dark);
+ position: relative;
+ overflow: hidden;
+}
+
+.top-bar {
+ height: 70px;
+ padding: 0 2rem;
+ background-color: rgba(15, 23, 42, 0.8);
+ backdrop-filter: blur(8px);
+ border-bottom: 1px solid var(--border-color);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.search-container {
+ position: relative;
+ width: 400px;
+}
+
+.search-container i {
+ position: absolute;
+ left: 1rem;
+ top: 50%;
+ transform: translateY(-50%);
+ color: var(--text-secondary);
+}
+
+.search-container input {
+ width: 100%;
+ padding: 0.6rem 1rem 0.6rem 2.8rem;
+ background-color: var(--bg-navbar);
+ border: 1px solid var(--border-color);
+ border-radius: 10px;
+ color: white;
+ outline: none;
+}
+
+.action-buttons {
+ display: flex;
+ align-items: center;
+ gap: 1.5rem;
+}
+
+.user-profile {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ overflow: hidden;
+ border: 2px solid var(--border-color);
+}
+
+.user-profile img {
+ width: 100%;
+}
+
+/* Dashboard Content */
+.dashboard-scroll {
+ flex: 1;
+ padding: 2rem;
+ overflow-y: auto;
+}
+
+.stats-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 1.5rem;
+ margin-bottom: 2rem;
+}
+
+.stat-card {
+ background-color: var(--card-bg);
+ border: 1px solid var(--border-color);
+ border-radius: 16px;
+ padding: 1.5rem;
+ display: flex;
+ align-items: center;
+ gap: 1.25rem;
+}
+
+.stat-icon {
+ width: 45px;
+ height: 45px;
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.25rem;
+}
+
+.stat-icon.purple { background: rgba(139, 92, 246, 0.15); color: var(--accent-purple); }
+.stat-icon.blue { background: rgba(59, 130, 246, 0.15); color: var(--accent-blue); }
+.stat-icon.orange { background: rgba(245, 158, 11, 0.15); color: var(--accent-orange); }
+.stat-icon.red { background: rgba(239, 68, 68, 0.15); color: var(--accent-red); }
+
+.stat-label {
+ display: block;
+ color: var(--text-secondary);
+ font-size: 0.85rem;
+ font-weight: 500;
+}
+
+.stat-info h2 {
+ font-size: 1.5rem;
+ font-weight: 700;
+}
+
+/* Content Sections */
+.content-grid {
+ display: grid;
+ grid-template-columns: 1.5fr 1fr;
+ gap: 1.5rem;
+}
+
+.card {
+ background-color: var(--card-bg);
+ border: 1px solid var(--border-color);
+ border-radius: 16px;
+ overflow: hidden;
+}
+
+.card-header {
+ padding: 1.25rem 1.5rem;
+ border-bottom: 1px solid var(--border-color);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.card-header h3 {
+ font-size: 1.1rem;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+}
+
+/* Table Styling */
+.table-container {
+ max-height: 500px;
+ overflow-y: auto;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+ text-align: left;
+}
+
+th {
+ padding: 0.75rem 1.5rem;
+ background-color: rgba(0, 0, 0, 0.1);
+ color: var(--text-secondary);
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ font-weight: 700;
+ letter-spacing: 0.5px;
+}
+
+td {
+ padding: 1rem 1.5rem;
+ border-bottom: 1px solid var(--border-color);
+ font-size: 0.9rem;
+}
+
+tr:last-child td { border-bottom: none; }
+
+.empty-state { text-align: center; color: var(--text-secondary); padding: 3rem; }
+
+/* Buttons */
+.btn {
+ padding: 0.6rem 1.2rem;
+ border-radius: 8px;
+ font-weight: 600;
+ cursor: pointer;
+ border: none;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ transition: var(--transition);
+}
+
+.btn-primary { background-color: var(--accent-purple); color: white; }
+.btn-primary:hover { background-color: #7c3aed; filter: brightness(1.1); }
+
+.btn-ghost { background: transparent; color: var(--text-secondary); border: 1px solid var(--border-color); }
+.btn-ghost:hover { border-color: var(--text-primary); color: white; }
+
+.btn-secondary-sm { padding: 0.4rem 0.8rem; font-size: 0.8rem; background: var(--bg-navbar); color: var(--accent-blue); border: 1px solid var(--accent-blue); }
+
+.btn-sm-success { background: rgba(16, 185, 129, 0.1); color: var(--accent-green); padding: 0.3rem 0.7rem; font-size: 0.75rem; }
+.btn-sm-danger { background: rgba(239, 68, 68, 0.1); color: var(--accent-red); padding: 0.3rem 0.7rem; font-size: 0.75rem; }
+
+/* Confidence Badge */
+.badge-conf {
+ padding: 0.2rem 0.6rem;
+ border-radius: 20px;
+ font-size: 0.75rem;
+ font-weight: 700;
+}
+
+.high { background: rgba(16, 185, 129, 0.2); color: var(--accent-green); }
+.med { background: rgba(245, 158, 11, 0.2); color: var(--accent-orange); }
+.low { background: rgba(239, 68, 68, 0.2); color: var(--accent-red); }
+
+/* Custom Scrollbar */
+::-webkit-scrollbar { width: 6px; }
+::-webkit-scrollbar-track { background: transparent; }
+::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 10px; }
+::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); }
+
+/* Toasts */
+#toast-container { position: fixed; bottom: 2rem; right: 2rem; display: flex; flex-direction: column; gap: 0.5rem; z-index: 1000; }
+.toast { background: var(--bg-navbar); color: white; padding: 1rem 1.5rem; border-radius: 10px; border-left: 4px solid var(--accent-purple); box-shadow: 0 10px 15px rgba(0,0,0,0.5); animation: slideIn 0.3s ease-out; }
+@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts
index e2be9aa..6215d40 100644
--- a/apps/api/src/app.module.ts
+++ b/apps/api/src/app.module.ts
@@ -2,6 +2,8 @@ import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ScheduleModule } from '@nestjs/schedule';
+import { ServeStaticModule } from '@nestjs/serve-static';
+import { join } from 'path';
import { TelemetryModule } from './telemetry/telemetry.module';
import { MapsModule } from './maps/maps.module';
import { GeocodingModule } from './geocoding/geocoding.module';
@@ -12,6 +14,10 @@ import { GeocodingModule } from './geocoding/geocoding.module';
isGlobal: true,
}),
ScheduleModule.forRoot(),
+ ServeStaticModule.forRoot({
+ rootPath: join(__dirname, '..', 'public', 'intel'),
+ serveRoot: '/api/intel',
+ }),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
diff --git a/apps/api/src/maps/road-stat.entity.ts b/apps/api/src/maps/road-stat.entity.ts
index 1240c43..b58f76e 100644
--- a/apps/api/src/maps/road-stat.entity.ts
+++ b/apps/api/src/maps/road-stat.entity.ts
@@ -12,6 +12,10 @@ export class RoadSegmentStat {
@Column('float', { default: 1.0 })
congestionFactor: number; // 1.0 = Free flow, >1.0 = Congested
+ @Column('boolean', { default: false })
+ @Index()
+ isClosed: boolean;
+
@Column('int', { default: 0 })
sampleCount: number;
diff --git a/infrastructure/docs/flutter_example.dart b/infrastructure/docs/flutter_example.dart
deleted file mode 100644
index 5446e25..0000000
--- a/infrastructure/docs/flutter_example.dart
+++ /dev/null
@@ -1,37 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_map/flutter_map.dart';
-import 'package:latlong2/latlong.dart';
-
-class IntaleqMapWidget extends StatelessWidget {
- @override
- Widget build(BuildContext context) {
- return FlutterMap(
- options: MapOptions(
- initialCenter: LatLng(33.5138, 36.2765), // Damascus / دمشق
- initialZoom: 13.0,
- ),
- children: [
- TileLayer(
- // هذا هو الرابط الذي يجلب الخريطة من السيرفر الخاص بك!
- urlTemplate: 'https://app.intaleqapp.com/martin/planet_osm_line/{z}/{x}/{y}.pbf',
- userAgentPackageName: 'com.intaleq.app',
- subdomains: const ['a', 'b', 'c'],
- ),
- ],
- );
- }
-}
-
-
-// ─────────────────────────────────────────────────────────
-// Helper Class for API Interaction (Intelligence API)
-// ─────────────────────────────────────────────────────────
-class IntaleqApiService {
- static const String baseUrl = 'https://app.intaleqapp.com/api';
- static const String apiKey = 'intaleq_secret_2026';
-
- Future triggerDailyIntelligence() async {
- // مثال لطلب معالجة البيانات من داخل تطبيق فلاتر (اختياري)
- print('Triggering 3 AM process manually...');
- }
-}
diff --git a/infrastructure/docs/flutter_maplibre_setup.dart b/infrastructure/docs/flutter_maplibre_setup.dart
deleted file mode 100644
index 35f9162..0000000
--- a/infrastructure/docs/flutter_maplibre_setup.dart
+++ /dev/null
@@ -1,69 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:maplibre_gl/maplibre_gl.dart';
-
-// ─────────────────────────────────────────────────────────
-// Intaleq Vector Map Widget (Professional Setup)
-// Uses MapLibre GL for High Performance Vector Tiles (.pbf)
-// ─────────────────────────────────────────────────────────
-class IntaleqMapWidget extends StatefulWidget {
- const IntaleqMapWidget({super.key});
-
- @override
- State createState() => _IntaleqMapWidgetState();
-}
-
-class _IntaleqMapWidgetState extends State {
- MaplibreMapController? mapController;
-
- void _onMapCreated(MaplibreMapController controller) {
- mapController = controller;
- }
-
- // Example: Downloading an offline region for caching
- Future _downloadOfflineRegion() async {
- final bounds = LatLngBounds(
- southwest: const LatLng(33.4, 36.1), // Southwest corner of Damascus
- northeast: const LatLng(33.6, 36.4), // Northeast corner of Damascus
- );
-
- // Mapbox/MapLibre has built-in offline caching for vector tiles!
- // It caches the vectors very efficiently.
- await downloadOfflineRegion(
- OfflineRegionDefinition(
- bounds: bounds,
- mapStyleUrl: 'https://app.intaleqapp.com/style-mobile.json', // Your custom style linking to .pbf
- minZoom: 10.0,
- maxZoom: 16.0,
- ),
- metadata: {
- 'name': 'Damascus Region',
- },
- );
- print('✅ Offline region downloaded and cached successfully!');
- }
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- body: MaplibreMap(
- onMapCreated: _onMapCreated,
- initialCameraPosition: const CameraPosition(
- target: LatLng(33.5138, 36.2765), // Damascus / دمشق
- zoom: 13.0,
- ),
- // The style URL points to a JSON file on your server.
- // This JSON defines colors, line widths, and the tile source (Martin .pbf URLs)
- styleString: 'https://app.intaleqapp.com/style-mobile.json',
-
- // Optimizations for smooth vector rendering
- minMaxZoomPreference: const MinMaxZoomPreference(5, 18),
- myLocationEnabled: true,
- ),
- floatingActionButton: FloatingActionButton(
- onPressed: _downloadOfflineRegion,
- tooltip: 'Download Offline Map',
- child: const Icon(Icons.download),
- ),
- );
- }
-}