Files
maps-saas/packages/tactical_app/lib/services/landmark_database.dart
T

397 lines
14 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../config/app_config.dart';
import '../models/landmark.dart';
class LandmarkDatabase {
static const String _storageKey = 'cached_tactical_landmarks_v1';
static List<TacticalLandmark> _dynamicLandmarks = [];
static bool _isLoaded = false;
static final List<TacticalLandmark> _builtInLandmarks = const [
// ── Amman Region (العاصمة عمان) ───────────────────────────
TacticalLandmark(
id: 'amm-1',
name: 'مئذنة مسجد الملك حسين (دابوق)',
region: 'عمان الغربية',
type: LandmarkType.minaret,
lat: 31.9835,
lng: 35.8285,
elevationM: 1010,
description: 'مئذنة حجرية بارزة ترى بوضوح من غرب وشمال العاصمة وشارع الأردن',
),
TacticalLandmark(
id: 'amm-2',
name: 'برج اتصالات جبل التاج / جبل القلعة',
region: 'وسط عمان',
type: LandmarkType.tower,
lat: 31.9540,
lng: 35.9350,
elevationM: 850,
description: 'برج إرسال رئيسي مرتفع يشرف على وسط البلد وشرق عمان',
),
TacticalLandmark(
id: 'amm-3',
name: 'خزان مياه دابوق العالي (البرج الدائري)',
region: 'عمان الغربية',
type: LandmarkType.waterTank,
lat: 31.9960,
lng: 35.8150,
elevationM: 1045,
description: 'خزان خرساني ضخم على أعلى تلة في دابوق',
),
TacticalLandmark(
id: 'amm-4',
name: 'برج اتصالات صويلح (الدوريات الخارجية)',
region: 'شمال عمان',
type: LandmarkType.tower,
lat: 32.0250,
lng: 35.8450,
elevationM: 1070,
description: 'أعلى نقطة رصد شمال غرب العاصمة على تقاطع طريق الشمال',
),
TacticalLandmark(
id: 'amm-5',
name: 'مئذنة مسجد الملك المؤسس عبدالله الأول (العبدلي)',
region: 'وسط عمان',
type: LandmarkType.minaret,
lat: 31.9615,
lng: 35.9130,
elevationM: 880,
description: 'قبة زرقاء شهيرة ومئذنتان واضحتان في قلب العبدلي',
),
TacticalLandmark(
id: 'amm-6',
name: 'أبراج بوابة الأردن (أبراج الدوار السادس)',
region: 'عمان الغربية',
type: LandmarkType.tower,
lat: 31.9580,
lng: 35.8680,
elevationM: 990,
description: 'برجان توأم زجاجيان عاليان يشاهدان من جميع أطراف العاصمة',
),
TacticalLandmark(
id: 'amm-7',
name: 'صوامع حبوب الجيزة / المطار',
region: 'جنوب عمان',
type: LandmarkType.grainSilo,
lat: 31.6980,
lng: 35.9520,
elevationM: 730,
description: 'منشأة صوامع خرسانية عالية على الطريق الصحراوي الرئيسي',
),
TacticalLandmark(
id: 'amm-8',
name: 'خزان مياه طبربور / عين غزال',
region: 'شمال شرق عمان',
type: LandmarkType.waterTank,
lat: 31.9890,
lng: 35.9420,
elevationM: 820,
description: 'برج مياه تكتيكي يشرف على اتوستراد عمان الزرقاء',
),
// ── Zarqa & Desert Corridor (الزرقاء والبادية) ─────────────
TacticalLandmark(
id: 'zrq-1',
name: 'مئذنة مسجد الشهيد راشد الزيود (غريسا)',
region: 'الزرقاء',
type: LandmarkType.minaret,
lat: 32.0910,
lng: 36.0950,
elevationM: 610,
description: 'مئذنة عالية تشرف على سهول الهاشمية وقرى الزرقاء',
),
TacticalLandmark(
id: 'zrq-2',
name: 'قاعدة الشهيد موفق السلطي الجوية (الأزرق)',
region: 'الأزرق / الصحراء الشرقية',
type: LandmarkType.militaryBase,
lat: 31.8320,
lng: 36.7860,
elevationM: 520,
description: 'قاعدة جوية رئيسية ومعلم عسكري إستراتيجي في البادية الشرقية',
),
TacticalLandmark(
id: 'zrq-3',
name: 'قلعة الأزرق التاريخية (قلعة البازلت)',
region: 'الأزرق',
type: LandmarkType.fort,
lat: 31.8800,
lng: 36.8270,
elevationM: 505,
description: 'حصن أثري من الحجر البازلتي الأسود يشرف على واحة الأزرق',
),
// ── North Region (إربد، عجلون، جرش، المفرق) ────────────────
TacticalLandmark(
id: 'irb-1',
name: 'برج اتصالات جامعة اليرموك (إربد)',
region: 'إربد',
type: LandmarkType.tower,
lat: 32.5340,
lng: 35.8540,
elevationM: 580,
description: 'برج إرسال رئيسي يشرف على كامل قاطع محافظة إربد وسهول حوران',
),
TacticalLandmark(
id: 'irb-2',
name: 'مئذنة مسجد إربد الكبير',
region: 'وسط إربد',
type: LandmarkType.minaret,
lat: 32.5560,
lng: 35.8490,
elevationM: 590,
description: 'مئذنة تاريخية مميزة في مركز المدينة القديمة',
),
TacticalLandmark(
id: 'ajl-1',
name: 'قلعة عجلون (قلعة الربض)',
region: 'عجلون',
type: LandmarkType.fort,
lat: 32.3250,
lng: 35.7270,
elevationM: 1020,
description: 'أعلى قمة عسكرية تاريخية تشرف على جبال الشمال وغور الأردن',
),
TacticalLandmark(
id: 'jrs-1',
name: 'أعمدة معبد أرتميس (جرش الأثرية)',
region: 'جرش',
type: LandmarkType.fort,
lat: 32.2815,
lng: 35.8910,
elevationM: 600,
description: 'أعمدة أثرية ضخمة ترى بوضوح من تلال جرش المقابلة',
),
TacticalLandmark(
id: 'mfq-1',
name: 'قاعدة الملك حسين الجوية (المفرق)',
region: 'المفرق',
type: LandmarkType.militaryBase,
lat: 32.3560,
lng: 36.2590,
elevationM: 680,
description: 'منشأة ومدرج طيران رئيسي في سهول الشمال الشرقي',
),
// ── Balqa & Salt (البلقاء والسلط والبحر الميت) ─────────────
TacticalLandmark(
id: 'slt-1',
name: 'مئذنة مسجد السلط الكبير',
region: 'السلط',
type: LandmarkType.minaret,
lat: 32.0390,
lng: 35.7280,
elevationM: 790,
description: 'مئذنة في وسط السلط تشرف على سفوح جبال البلقاء',
),
TacticalLandmark(
id: 'slt-2',
name: 'قمة جبل نيبو (النبي موسى - مادبا)',
region: 'مادبا / البلقاء',
type: LandmarkType.mountain,
lat: 31.7680,
lng: 35.7250,
elevationM: 810,
description: 'مطل استراتيجي مرتفع يطل على وادي الأردن والبحر الميت والقدس',
),
TacticalLandmark(
id: 'dms-1',
name: 'نقطة المراقبة البحرية (شمال البحر الميت)',
region: 'البحر الميت',
type: LandmarkType.militaryBase,
lat: 31.7200,
lng: 35.5800,
elevationM: -390,
description: 'برج مراقبة على الشاطئ الشمالي لأخفض نقطة على سطح الأرض',
),
// ── South Region (الكرك، الطفيلة، معان، العقبة) ───────────
TacticalLandmark(
id: 'krk-1',
name: 'قلعة الكرك التاريخية (برج الحصن)',
region: 'الكرك',
type: LandmarkType.fort,
lat: 31.1810,
lng: 35.7020,
elevationM: 950,
description: 'حصن صخري منيع يشرف على كامل قاطع الكرك والوديان الغربية',
),
TacticalLandmark(
id: 'taf-1',
name: 'برج اتصالات جبل العيص (الطفيلة)',
region: 'الطفيلة',
type: LandmarkType.tower,
lat: 30.8420,
lng: 35.6180,
elevationM: 1250,
description: 'أعلى قمة في قاطع الطفيلة عليها أبراج رادار واتصالات',
),
TacticalLandmark(
id: 'man-1',
name: 'قلعة الشوبك (مونتريال)',
region: 'معان / الشوبك',
type: LandmarkType.fort,
lat: 30.5315,
lng: 35.5610,
elevationM: 1330,
description: 'قلعة مخروطية عالية فوق تلة منعزلة ترى من مسافات بعيدة',
),
TacticalLandmark(
id: 'aqb-1',
name: 'سارية علم الثورة العربية الكبرى (ساحل العقبة)',
region: 'العقبة',
type: LandmarkType.tower,
lat: 29.5215,
lng: 35.0015,
elevationM: 135,
description: 'سارية علم بطول 130 متراً تشاهد من كامل خليج العقبة والحدود',
),
TacticalLandmark(
id: 'aqb-2',
name: 'صوامع ميناء العقبة الجنوبي',
region: 'العقبة',
type: LandmarkType.grainSilo,
lat: 29.4120,
lng: 34.9810,
elevationM: 25,
description: 'صوامع ومنشآت تخزين عملاقة على الساحل الجنوبي',
),
];
static List<TacticalLandmark> get allLandmarks {
if (_dynamicLandmarks.isEmpty) {
return _builtInLandmarks;
}
// Merge dynamic with built-in by unique ID
final map = <String, TacticalLandmark>{};
for (final lm in _builtInLandmarks) {
map[lm.id] = lm;
}
for (final lm in _dynamicLandmarks) {
map[lm.id] = lm;
}
return map.values.toList();
}
/// Initialize local cache from persistent storage (permanent documents directory)
static Future<void> initCache() async {
if (_isLoaded) return;
try {
// 1. Try reading from permanent application documents file
final dir = await getApplicationDocumentsDirectory();
final dbFile = File('${dir.path}/jordan_tactical_landmarks.json');
if (await dbFile.exists()) {
final rawJson = await dbFile.readAsString();
if (rawJson.isNotEmpty) {
final List<dynamic> list = jsonDecode(rawJson);
_dynamicLandmarks = list.map((item) => _parseLandmark(item)).toList();
_isLoaded = true;
return;
}
}
// 2. Fallback to SharedPreferences
final prefs = await SharedPreferences.getInstance();
final rawJson = prefs.getString(_storageKey);
if (rawJson != null && rawJson.isNotEmpty) {
final List<dynamic> list = jsonDecode(rawJson);
_dynamicLandmarks = list.map((item) => _parseLandmark(item)).toList();
}
_isLoaded = true;
} catch (e) {
debugPrint('Landmark cache init error: $e');
}
}
/// Sync landmarks from Server API and update local permanent storage
static Future<int> syncFromServer({
String? serverUrl,
String? apiKey,
}) async {
final activeUrl = serverUrl ?? AppConfig.serverUrl;
final activeKey = apiKey ?? AppConfig.apiKey;
try {
final url = Uri.parse('$activeUrl/api/tactical/landmarks');
final resp = await http.get(url, headers: {'x-api-key': activeKey}).timeout(const Duration(seconds: 10));
if (resp.statusCode == 200) {
final data = jsonDecode(resp.body);
if (data['landmarks'] != null && data['landmarks'] is List) {
final List<dynamic> rawList = data['landmarks'];
_dynamicLandmarks = rawList.map((item) => _parseLandmark(item)).toList();
// Save to permanent documents directory
try {
final dir = await getApplicationDocumentsDirectory();
final dbFile = File('${dir.path}/jordan_tactical_landmarks.json');
await dbFile.writeAsString(jsonEncode(rawList));
} catch (fileErr) {
debugPrint('Error writing landmarks file: $fileErr');
}
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_storageKey, jsonEncode(rawList));
return _dynamicLandmarks.length;
}
}
} catch (e) {
debugPrint('Sync landmarks error: $e');
}
return _dynamicLandmarks.length;
}
static TacticalLandmark _parseLandmark(dynamic json) {
LandmarkType parseType(String? t) {
switch (t) {
case 'minaret': return LandmarkType.minaret;
case 'tower': return LandmarkType.tower;
case 'waterTank': return LandmarkType.waterTank;
case 'fort': return LandmarkType.fort;
case 'mountain': return LandmarkType.mountain;
case 'militaryBase': return LandmarkType.militaryBase;
case 'grainSilo': return LandmarkType.grainSilo;
default: return LandmarkType.tower;
}
}
return TacticalLandmark(
id: json['id'] ?? 'lm-${DateTime.now().millisecondsSinceEpoch}',
name: json['name'] ?? 'معلم تكتيكي',
region: json['region'] ?? 'الأردن',
type: parseType(json['type']),
lat: (json['lat'] as num).toDouble(),
lng: (json['lng'] as num).toDouble(),
elevationM: (json['elevationM'] as num?)?.toInt() ?? 800,
description: json['description'] ?? '',
);
}
static List<TacticalLandmark> search(String query, {String? region, LandmarkType? type}) {
final cleanQuery = query.trim().toLowerCase();
return allLandmarks.where((landmark) {
if (region != null && region.isNotEmpty && region != 'الكل') {
if (!landmark.region.contains(region)) return false;
}
if (type != null) {
if (landmark.type != type) return false;
}
if (cleanQuery.isEmpty) return true;
return landmark.name.toLowerCase().contains(cleanQuery) ||
landmark.region.toLowerCase().contains(cleanQuery) ||
landmark.description.toLowerCase().contains(cleanQuery) ||
landmark.typeLabelArabic.contains(cleanQuery);
}).toList();
}
}