stable: finalized GIS aesthetics and Syria/Jordan routing synchronization

This commit is contained in:
Hamza-Ayed
2026-03-28 04:12:01 +03:00
parent d6f3b41b53
commit 592af8ce5c
203 changed files with 9711 additions and 119 deletions
+9
View File
@@ -0,0 +1,9 @@
Downloading Syria map data...
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
␍ 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0␍100 239 100 239 0 0 829 0 --:--:-- --:--:-- --:--:-- 829
Warning: Failed to open the file
Warning: /home/hamzadoctor/app/infrastructure/osm-data/syria-latest.osm.pbf:
Warning: No such file or directory
␍ 0 73.8M 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
curl: (23) Failure writing output to destination, passed 4096 returned 4294967295
Vendored
BIN
View File
Binary file not shown.
+6
View File
@@ -22,3 +22,9 @@ TILE_SERVER_URL=http://tileserver:8080
API_PORT=3200 API_PORT=3200
WEB_PORT=3201 WEB_PORT=3201
NODE_ENV=development NODE_ENV=development
# External Location Server Integration (Every 10 days)
# إعدادات الاتصال بسيرفر المواقع الخارجي
LOCATION_SERVER_URL=https://location.intaleq.xyz/location
LOCATION_SERVER_API_KEY=intaleq_secret_2026
+116
View File
@@ -0,0 +1,116 @@
# Map SaaS Platform - Hyper-Detailed Engineering Guide / الدليل الهندسي التفصيلي العميق لمنصة خرائط ساس
This is the definitive, "ground-zero" guide to the Map SaaS platform. It is designed to explain not just *how* the code works, but the engineering *philosophy* and the "Why" behind every architectural decision.
هذا هو الدليل المرجعي والنهائي لمنصة **Map SaaS**. تم تصميمه لشرح ليس فقط *كيفية* عمل الكود، ولكن أيضاً *الفلسفة* الهندسية و"السبب" وراء كل قرار معماري تم اتخاذه.
---
## 1. Project Philosophy: Why Own the Infrastructure? / فلسفة المشروع: لماذا نمتلك البنية التحتية؟
**English:**
Most developers use Google Maps or Mapbox APIs. While easy, these are expensive and restrict how you use the data. This project is built on the philosophy of **Sovereignty**. By using OpenStreetMap (OSM), PostGIS, and Martin, we own the entire stack. We don't pay per map view, and we can customize the map logic (like routing rules for specific vehicles) to a level impossible with commercial APIs.
**عربي:**
معظم المطورين يستخدمون واجهات برمجة تطبيقات (APIs) مثل خرائط جوجل أو Mapbox. رغم سهولتها، إلا أنها مكلفة وتقيد كيفية استخدامك للبيانات. تم بناء هذا المشروع على فلسفة **السيادة التقنية**. من خلال استخدام OpenStreetMap (OSM) و PostGIS و Martin، نحن نمتلك "الكومة" التقنية بالكامل. نحن لا ندفع مقابل كل عرض للخريطة، ويمكننا تخصيص منطق الخريطة (مثل قواعد التوجيه لمركبات معينة) بمستوى مستحيل مع واجهات برمجة التطبيقات التجارية.
---
## 2. Infrastructure Deep-Dive (Docker Compose) / التعمق في البنية التحتية (Docker Compose)
**English:**
We use Docker Compose to orchestrate 6 distinct services. Each service is isolated, ensuring that a bug in the routing engine doesn't crash the database.
**عربي:**
نستخدم Docker Compose لتنظيم 6 خدمات متميزة. كل خدمة معزولة، مما يضمن أن أي عطل في محرك التوجيه لن يؤدي إلى انهيار قاعدة البيانات.
### Service-by-Service Breakdown / تفصيل الخدمات من الناحية التقنية:
#### A. PostGIS (`db`)
- **Why? / لماذا؟:** Standard PostgreSQL cannot understand that a "Point" at (35.9, 31.9) is "inside" a "Polygon". PostGIS adds spatial types and functions.
- **Arabic / تفصيل بالعربي:** قاعدة بيانات PostgreSQL العادية لا تستطيع فهم أن "نقطة" في الإحداثيات (35.9, 31.9) تقع "داخل" شكل هندسي (Polygon). إضافة PostGIS تضيف أنواعاً وظائف مكانية تسمح لنا بإجراء استعلامات SQL مثل: "ابحث عن أقرب صيدلية لهذا الموقع".
- **Config / الإعداد:** `image: postgis/postgis:15-3.3` -> We use version 15 for stability and PostGIS 3.3 for the latest spatial performance.
#### B. Martin (`martin`)
- **Why? / لماذا؟:** This is our **Tile Server**. Instead of pre-rendering millions of PNG images (which takes terabytes of space), Martin generates **Vector Tiles (MVT)** on-the-fly.
- **Arabic / تفصيل بالعربي:** هذا هو **خادم البلاطات**. بدلاً من الرصد المسبق لملايين صور PNG (التي تستهلك مساحات هائلة)، يقوم Martin بإنشاء **بلاطات المتجهات (MVT)** بشكل فوري عند الطلب من قاعدة البيانات.
- **Logic / المنطق:** It connects directly to PostGIS via the `DATABASE_URL` and serves tables as map layers.
#### C. GraphHopper (`routing`)
- **Why? / لماذا؟:** While PostGIS can find distances, it doesn't know about one-way streets, traffic lights, or speed limits. GraphHopper converts OSM data into a mathematical "Graph" of nodes and edges to find the mathematically shortest/fastest path.
- **Arabic / تفصيل بالعربي:** رغم أن PostGIS يمكنه حساب المسافات، إلا أنه لا يعرف شيئاً عن الشوارع ذات الاتجاه الواحد، أو إشارات المرور، أو حدود السرعة. يقوم GraphHopper بتحويل بيانات OSM إلى "رسم بياني" رياضي (Graph) مكون من عقد وحواف للعثور على أقصر أو أسرع مسار رياضياً.
#### D. API (`api`) & Web (`web`)
- **Why? / لماذا؟:** The API acts as a gateway (using NestJS) to bundle logic and proxy requests. The Web (React/Vite) provides the visual interface using MapLibre GL.
- **Arabic / تفصيل بالعربي:** يعمل الـ API كبوابة (باستخدام NestJS) لتجميع المنطق وتوجيه الطلبات. يوفر الـ Web (React/Vite) الواجهة المرئية باستخدام MapLibre GL لقراءة بلاطات المتجهات ورسمها.
---
## 3. Backend Logic: The Brain / منطق الخلفية البرمجية: العقل
**English:**
The API is built using **NestJS**. We chose this because of its modular architecture, which mirrors our microservices.
**عربي:**
تم بناء الـ API باستخدام **NestJS**. اخترنا هذا بسبب هيكليته المودولية (Modular Architecture)، والتي تعكس خدماتنا المصغرة.
### Maps Module Deep-Dive / تعمق في موديول الخرائط:
#### `maps.controller.ts`
- **EN:** Handles HTTP requests like `GET /maps/route`. It validates input before passing it to the service.
- **AR:** يتعامل مع طلبات HTTP مثل `GET /maps/route`. يقوم بالتحقق من صحة المدخلات قبل تمريرها إلى الخدمة (Service).
#### `maps.service.ts`
- **EN:** This is where the heavy lifting happens. It communicates with GraphHopper.
- **AR:** هنا يحدث العمل الفعلي الثقيل. تتواصل هذه الخدمة مع GraphHopper.
- **Why Proxy? / لماذا التوجيه البرمجي؟:** We proxy GraphHopper through the API so that:
1. We can add authentication. / يمكننا إضافة مصادقة (Security).
2. We can cache frequent routes in **Redis**. / يمكننا تخزين المسارات المتكررة في **Redis** لزيادة السرعة.
3. We hide the internal infrastructure (like port 8989) from the public internet. / نخفي البنية التحتية الداخلية عن الإنترنت العام.
---
## 4. Frontend Logic: The Interface / منطق الواجهة الأمامية: الواجهة
**English:**
The frontend uses **MapLibre GL**, an open-source library for high-performance interactive maps.
**عربي:**
تستخدم الواجهة الأمامية **MapLibre GL**، وهي مكتبة مفتوحة المصدر للخرائط التفاعلية عالية الأداء.
### `MapComponent.tsx` Explained / شرح مكون الخريطة:
- **Sources & Layers / المصادر والطبقات:**
- **EN:** We define **Sources** (where the data comes from, i.e., Martin) and **Layers** (how the data looks, e.g., coloring roads blue).
- **AR:** نقوم بتعريف **المصادر (Sources)** (من أين تأتي البيانات، أي من Martin) و **الطبقات (Layers)** (كيف تبدو البيانات، مثل تلوين الطرق باللون الأزرق).
- **3D Logic / منطق ثلاثي الأبعاد:**
- We use the `fill-extrusion` layer type to turn 2D building outlines into 3D blocks by calculating heights from OSM data.
- نستخدم نوع الطبقة `fill-extrusion` لتحويل حدود المباني ثنائية الأبعاد إلى كتل ثلاثية الأبعاد من خلال حساب الارتفاعات من بيانات OSM.
---
## 5. Command Tutorial: Step-by-Step / دليل الأوامر: خطوة بخطوة
### 1. `docker-compose up -d`
- **Why? / لماذا؟:** `-d` stands for "detached". It runs the servers in the background so your terminal remains free.
- **AR:** حرف `-d` يعني "Detached". يقوم بتشغيل الخوادم في الخلفية حتى يظل التيرمينال (Terminal) متاحاً لك للاستخدام.
### 2. `docker-compose run --rm osm-import`
- **Why? / لماذا؟:**
- `run`: Starts the importer. / يبدأ عملية الاستيراد.
- `--rm`: Automatically removes the container after it finishes to save disk space. / يحذف الحاوية تلقائياً بعد الانتهاء لتوفير مساحة القرص.
- **Technical Goal:** It uses `osm2pgsql` to convert the `jordan-latest.osm.pbf` file (raw data) into relational SQL tables in PostGIS.
- **الهدف التقني:** يستخدم أداة `osm2pgsql` لتحويل ملف بيانات الأردن (البيانات الخام) إلى جداول SQL علائقية داخل PostGIS ليتمكن Martin من قراءتها.
---
## 6. Glossary of Terms (Advanced) / قاموس المصطلحات (متقدم)
- **MVT (Mapbox Vector Tiles):** The binary format we use to transmit map data over the wire. Small and fast. / التنسيق الثنائي الذي نستخدمه لنقل بيانات الخريطة عبر الشبكة. صغير وسريع.
- **PBF (Protocolbuffer Binary Format):** The compressed format for OpenStreetMap data. / التنسيق المضغوط لبيانات OpenStreetMap.
- **Reverse Proxy:** The pattern of the API sitting between the user and internal services like GraphHopper. / نمط عمل الـ API عندما يقع بين المستخدم والخدمات الداخلية (مثل GraphHopper) لحمايتها وإدارتها.
- **Schema:** The structure of the database tables where map data is stored. / هيكلية جداول قاعدة البيانات حيث يتم تخزين بيانات الخريطة.
---
*This guide is your roadmap. Use it to build the future of mapping! / هذا الدليل هو خارطة الطريق الخاصة بك. استخدمه لبناء مستقبل الخرائط!*
+66
View File
@@ -0,0 +1,66 @@
# دليل نظام خرائط الأردن (Map SaaS) 🗺️
هذا الدليل يشرح المكونات التقنية للنظام والأوامر المستخدمة لإدارته باللغة العربية.
## 1. المكونات الأساسية للنظام 🏗️
النظام يعتمد على تقنية **Docker**، وهي تقنية تسمح بتشغيل التطبيقات في بيئات معزولة تسمى "حاويات" (Containers).
### أ. خادم الويب (Web Dashboard)
- **الوظيفة**: الواجهة التي يراها المستخدم.
- **التقنية**: React + Vite.
- **المنفذ المحلي**: 3201.
### ب. الواجهة البرمجية (API Backend)
- **الوظيفة**: معالجة المنطق البرمجي، حساب المسارات، والتعامل مع البيانات.
- **التقنية**: NestJS.
- **المنفذ المحلي**: 3200.
### ج. خادم الخرائط (Martin Tile Server)
- **الوظيفة**: تحويل البيانات الجغرافية من قاعدة البيانات إلى "مربعات" (Tiles) يمكن عرضها على المتصفح.
- **المنفذ المحلي**: 3202.
### د. محرك المسارات (GraphHopper)
- **الوظيفة**: حساب أسرع طريق بين نقطتين مع مراعاة الازدحامات المرورية.
- **المنفذ المحلي**: 8080 (داخلي).
---
## 2. أوامر التحكم الأساسية (Docker Commands) 💻
إليك الأوامر التي استخدمناها وكيف تعمل:
- **`docker compose up -d`**:
- تشغيل كل خدمات النظام في الخلفية.
- حرف `-d` يعني "Detached" (أي يعمل في الخلفية دون إغلاق التيرمينال).
- **`docker compose restart [service_name]`**:
- إعادة تشغيل خدمة معينة (مثلاً `martin` أو `api`) لتحديث الإعدادات.
- **`docker compose logs -f [service_name]`**:
- عرض سجلات العمليات (Logs) للخدمة. مفيد جداً لاستكشاف الأخطاء.
- **`docker compose run --rm osm-import`**:
- أمر خاص لتشغيل عملية استيراد خرائط الأردن (OSM) إلى قاعدة البيانات.
- العلم `--rm` يعني مسح الحاوية المؤقنة بعد الانتهاء.
---
## 3. تحسينات الأداء والأمان 🔐
### أ. التشفير (Polyline Encoding)
لقد قمنا بتغيير طريقة إرسال المسار من "قائمة إحداثيات طويلة" إلى "نص مشفر" (Polyline). هذا يقلل حجم البيانات بنسبة تصل إلى 90%، مما يجعل التطبيق أسرع بكثير على الهواتف.
### ب. مفتاح الأمان (API Key)
تم إضافة طبقة حماية للمنع الاستخدام غير المصرح به للواجهة البرمجية. يجب إرسال مفتاح سري (`x-api-key`) في ترويسة كل طلب.
---
## 4. كيفية إضافة خرائط جديدة (مثل سوريا) 🇸🇾
الأمر بسيط جداً بفضل استخدام Docker:
1. تحميل ملف الخريطة بصيغة `.osm.pbf`.
2. دمج الملف مع خارطة الأردن باستخدام أداة `osmium`.
3. إعادة تشغيل أمر الاستيراد (`osm-import`).
---
🔧 **ملاحظة**: جميع الإعدادات محفوظة في ملف `.env`. لا تقم بتغييره إلا إذا كنت متأكداً من المنافذ وكلمات المرور.
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
set -e
DATA_DIR="/home/hamzadoctor/app/infrastructure/osm-data"
echo "Downloading Syria map data..."
curl -L -o "$DATA_DIR/syria-latest.osm.pbf" https://download.geofabrik.de/asia/syria-latest.osm.pbf
echo "Merging Syria and Jordan..."
# Backup original Jordan if not already backed up
if [ ! -f "$DATA_DIR/jordan-latest.orig.osm.pbf" ]; then
cp "$DATA_DIR/jordan-latest.osm.pbf" "$DATA_DIR/jordan-latest.orig.osm.pbf"
fi
# Use --overwrite flag and ensure .pbf extension is at the end
osmium merge "$DATA_DIR/jordan-latest.orig.osm.pbf" "$DATA_DIR/syria-latest.osm.pbf" -o "$DATA_DIR/levant-latest.osm.pbf" --overwrite
echo "Replacing data file for import..."
mv "$DATA_DIR/levant-latest.osm.pbf" "$DATA_DIR/jordan-latest.osm.pbf"
echo "Re-importing to database (this may take a few minutes)..."
cd /home/hamzadoctor/app
docker compose run --rm osm-import
echo "Restarting services..."
docker compose restart martin api
echo "✅ Success! Jordan and Syria are now merged and imported."
+6
View File
@@ -0,0 +1,6 @@
import { AppService } from './app.service';
export declare class AppController {
private readonly appService;
constructor(appService: AppService);
getHello(): string;
}
+35
View File
@@ -0,0 +1,35 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppController = void 0;
const common_1 = require("@nestjs/common");
const app_service_1 = require("./app.service");
let AppController = class AppController {
appService;
constructor(appService) {
this.appService = appService;
}
getHello() {
return this.appService.getHello();
}
};
exports.AppController = AppController;
__decorate([
(0, common_1.Get)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", String)
], AppController.prototype, "getHello", null);
exports.AppController = AppController = __decorate([
(0, common_1.Controller)(),
__metadata("design:paramtypes", [app_service_1.AppService])
], AppController);
//# sourceMappingURL=app.controller.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"app.controller.js","sourceRoot":"","sources":["../src/app.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAiD;AACjD,+CAA2C;AAGpC,IAAM,aAAa,GAAnB,MAAM,aAAa;IACK;IAA7B,YAA6B,UAAsB;QAAtB,eAAU,GAAV,UAAU,CAAY;IAAG,CAAC;IAGvD,QAAQ;QACN,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;IACpC,CAAC;CACF,CAAA;AAPY,sCAAa;AAIxB;IADC,IAAA,YAAG,GAAE;;;;6CAGL;wBANU,aAAa;IADzB,IAAA,mBAAU,GAAE;qCAE8B,wBAAU;GADxC,aAAa,CAOzB"}
+2
View File
@@ -0,0 +1,2 @@
export declare class AppModule {
}
+58
View File
@@ -0,0 +1,58 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppModule = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const typeorm_1 = require("@nestjs/typeorm");
const schedule_1 = require("@nestjs/schedule");
const telemetry_module_1 = require("./telemetry/telemetry.module");
const maps_module_1 = require("./maps/maps.module");
const geocoding_module_1 = require("./geocoding/geocoding.module");
let AppModule = class AppModule {
};
exports.AppModule = AppModule;
exports.AppModule = AppModule = __decorate([
(0, common_1.Module)({
imports: [
config_1.ConfigModule.forRoot({
isGlobal: true,
}),
schedule_1.ScheduleModule.forRoot(),
typeorm_1.TypeOrmModule.forRootAsync({
imports: [config_1.ConfigModule],
inject: [config_1.ConfigService],
useFactory: (config) => ({
type: 'postgres',
url: config.get('DATABASE_URL'),
autoLoadEntities: true,
synchronize: true,
}),
}),
typeorm_1.TypeOrmModule.forRootAsync({
name: 'mysqlConnection',
imports: [config_1.ConfigModule],
inject: [config_1.ConfigService],
useFactory: (config) => ({
type: 'mysql',
host: config.get('GEOCODE_DB_HOST', 'localhost'),
port: config.get('GEOCODE_DB_PORT', 3306),
username: config.get('GEOCODE_DB_USER', 'root'),
password: config.get('GEOCODE_DB_PASS', ''),
database: config.get('GEOCODE_DB_NAME', 'geocodeDB'),
autoLoadEntities: true,
synchronize: false,
}),
}),
telemetry_module_1.TelemetryModule,
maps_module_1.MapsModule,
geocoding_module_1.GeocodingModule,
],
})
], AppModule);
//# sourceMappingURL=app.module.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"app.module.js","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,2CAA6D;AAC7D,6CAAgD;AAChD,+CAAkD;AAClD,mEAA+D;AAC/D,oDAAgD;AAChD,mEAA+D;AAuCxD,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,8BAAS;oBAAT,SAAS;IArCrB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,qBAAY,CAAC,OAAO,CAAC;gBACnB,QAAQ,EAAE,IAAI;aACf,CAAC;YACF,yBAAc,CAAC,OAAO,EAAE;YACxB,uBAAa,CAAC,YAAY,CAAC;gBACzB,OAAO,EAAE,CAAC,qBAAY,CAAC;gBACvB,MAAM,EAAE,CAAC,sBAAa,CAAC;gBACvB,UAAU,EAAE,CAAC,MAAqB,EAAE,EAAE,CAAC,CAAC;oBACtC,IAAI,EAAE,UAAU;oBAChB,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC;oBAC/B,gBAAgB,EAAE,IAAI;oBACtB,WAAW,EAAE,IAAI;iBAElB,CAAC;aACH,CAAC;YACF,uBAAa,CAAC,YAAY,CAAC;gBACzB,IAAI,EAAE,iBAAiB;gBACvB,OAAO,EAAE,CAAC,qBAAY,CAAC;gBACvB,MAAM,EAAE,CAAC,sBAAa,CAAC;gBACvB,UAAU,EAAE,CAAC,MAAqB,EAAE,EAAE,CAAC,CAAC;oBACtC,IAAI,EAAE,OAAO;oBACb,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,iBAAiB,EAAE,WAAW,CAAC;oBAChD,IAAI,EAAE,MAAM,CAAC,GAAG,CAAS,iBAAiB,EAAE,IAAI,CAAC;oBACjD,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC;oBAC/C,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,iBAAiB,EAAE,EAAE,CAAC;oBAC3C,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,iBAAiB,EAAE,WAAW,CAAC;oBACpD,gBAAgB,EAAE,IAAI;oBACtB,WAAW,EAAE,KAAK;iBACnB,CAAC;aACH,CAAC;YACF,kCAAe;YACf,wBAAU;YACV,kCAAe;SAChB;KACF,CAAC;GACW,SAAS,CAAG"}
+3
View File
@@ -0,0 +1,3 @@
export declare class AppService {
getHello(): string;
}
+20
View File
@@ -0,0 +1,20 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppService = void 0;
const common_1 = require("@nestjs/common");
let AppService = class AppService {
getHello() {
return 'Hello World!';
}
};
exports.AppService = AppService;
exports.AppService = AppService = __decorate([
(0, common_1.Injectable)()
], AppService);
//# sourceMappingURL=app.service.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"app.service.js","sourceRoot":"","sources":["../src/app.service.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA4C;AAGrC,IAAM,UAAU,GAAhB,MAAM,UAAU;IACrB,QAAQ;QACN,OAAO,cAAc,CAAC;IACxB,CAAC;CACF,CAAA;AAJY,gCAAU;qBAAV,UAAU;IADtB,IAAA,mBAAU,GAAE;GACA,UAAU,CAItB"}
+9
View File
@@ -0,0 +1,9 @@
import { ConfigService } from '@nestjs/config';
export declare class CacheService {
private configService;
private client;
constructor(configService: ConfigService);
set(key: string, value: any, ttlSeconds?: number): Promise<void>;
get(key: string): Promise<any>;
del(key: string): Promise<void>;
}
+43
View File
@@ -0,0 +1,43 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CacheService = void 0;
const common_1 = require("@nestjs/common");
const redis_1 = require("redis");
const config_1 = require("@nestjs/config");
let CacheService = class CacheService {
configService;
client;
constructor(configService) {
this.configService = configService;
const redisUrl = this.configService.get('REDIS_URL', 'redis://redis:6379');
this.client = (0, redis_1.createClient)({ url: redisUrl });
this.client.connect().catch(console.error);
}
async set(key, value, ttlSeconds = 3600) {
await this.client.set(key, JSON.stringify(value), {
EX: ttlSeconds,
});
}
async get(key) {
const data = await this.client.get(key);
return data ? JSON.parse(data) : null;
}
async del(key) {
await this.client.del(key);
}
};
exports.CacheService = CacheService;
exports.CacheService = CacheService = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [config_1.ConfigService])
], CacheService);
//# sourceMappingURL=cache.service.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"cache.service.js","sourceRoot":"","sources":["../../src/common/cache.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAoD;AACpD,iCAAqC;AACrC,2CAA+C;AAGxC,IAAM,YAAY,GAAlB,MAAM,YAAY;IAGH;IAFZ,MAAM,CAAC;IAEf,YAAoB,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;QAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAC;QAC3E,IAAI,CAAC,MAAM,GAAG,IAAA,oBAAY,EAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC9C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAU,EAAE,aAAqB,IAAI;QAG1D,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE;YAChD,EAAE,EAAE,UAAU;SACf,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QAGnB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACxC,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;CACF,CAAA;AA3BY,oCAAY;uBAAZ,YAAY;IADxB,IAAA,mBAAU,GAAE;qCAIwB,sBAAa;GAHrC,YAAY,CA2BxB"}
+8
View File
@@ -0,0 +1,8 @@
export declare const JORDAN_BOUNDS: {
minLat: number;
maxLat: number;
minLng: number;
maxLng: number;
};
export declare function isWithinJordan(lat: number, lng: number): boolean;
export declare function mockMapMatch(lat: number, lng: number): string;
+21
View File
@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.JORDAN_BOUNDS = void 0;
exports.isWithinJordan = isWithinJordan;
exports.mockMapMatch = mockMapMatch;
exports.JORDAN_BOUNDS = {
minLat: 29.1,
maxLat: 33.4,
minLng: 34.9,
maxLng: 39.3,
};
function isWithinJordan(lat, lng) {
return (lat >= exports.JORDAN_BOUNDS.minLat &&
lat <= exports.JORDAN_BOUNDS.maxLat &&
lng >= exports.JORDAN_BOUNDS.minLng &&
lng <= exports.JORDAN_BOUNDS.maxLng);
}
function mockMapMatch(lat, lng) {
return `osm_way_${Math.floor(Math.random() * 1000000)}`;
}
//# sourceMappingURL=gis.utils.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"gis.utils.js","sourceRoot":"","sources":["../../src/common/gis.utils.ts"],"names":[],"mappings":";;;AAYA,wCAOC;AAMD,oCAGC;AAvBY,QAAA,aAAa,GAAG;IAC3B,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,IAAI;CACb,CAAC;AAEF,SAAgB,cAAc,CAAC,GAAW,EAAE,GAAW;IACrD,OAAO,CACL,GAAG,IAAI,qBAAa,CAAC,MAAM;QAC3B,GAAG,IAAI,qBAAa,CAAC,MAAM;QAC3B,GAAG,IAAI,qBAAa,CAAC,MAAM;QAC3B,GAAG,IAAI,qBAAa,CAAC,MAAM,CAC5B,CAAC;AACJ,CAAC;AAMD,SAAgB,YAAY,CAAC,GAAW,EAAE,GAAW;IAEnD,OAAO,WAAW,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC;AAC1D,CAAC"}
+7
View File
@@ -0,0 +1,7 @@
import { CanActivate, ExecutionContext } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
export declare class ApiKeyGuard implements CanActivate {
private configService;
constructor(configService: ConfigService);
canActivate(context: ExecutionContext): boolean;
}
+35
View File
@@ -0,0 +1,35 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiKeyGuard = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
let ApiKeyGuard = class ApiKeyGuard {
configService;
constructor(configService) {
this.configService = configService;
}
canActivate(context) {
const request = context.switchToHttp().getRequest();
const apiKeyHeader = request.headers['x-api-key'];
const validApiKey = this.configService.get('API_KEY');
if (validApiKey && apiKeyHeader !== validApiKey) {
throw new common_1.UnauthorizedException('Invalid or missing API Key');
}
return true;
}
};
exports.ApiKeyGuard = ApiKeyGuard;
exports.ApiKeyGuard = ApiKeyGuard = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [config_1.ConfigService])
], ApiKeyGuard);
//# sourceMappingURL=api-key.guard.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"api-key.guard.js","sourceRoot":"","sources":["../../../src/common/guards/api-key.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAKwB;AACxB,2CAA+C;AAGxC,IAAM,WAAW,GAAjB,MAAM,WAAW;IACF;IAApB,YAAoB,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;IAAG,CAAC;IAEpD,WAAW,CAAC,OAAyB;QACnC,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QACpD,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAClD,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAS,SAAS,CAAC,CAAC;QAG9D,IAAI,WAAW,IAAI,YAAY,KAAK,WAAW,EAAE,CAAC;YAChD,MAAM,IAAI,8BAAqB,CAAC,4BAA4B,CAAC,CAAC;QAChE,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AAfY,kCAAW;sBAAX,WAAW;IADvB,IAAA,mBAAU,GAAE;qCAEwB,sBAAa;GADrC,WAAW,CAevB"}
+2
View File
@@ -0,0 +1,2 @@
export declare class RedisModule {
}
+36
View File
@@ -0,0 +1,36 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RedisModule = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const redis_1 = require("redis");
const redis_service_1 = require("./redis.service");
let RedisModule = class RedisModule {
};
exports.RedisModule = RedisModule;
exports.RedisModule = RedisModule = __decorate([
(0, common_1.Global)(),
(0, common_1.Module)({
providers: [
{
provide: 'REDIS_CLIENT',
useFactory: async (configService) => {
const url = configService.get('REDIS_URL', 'redis://redis:6379');
const client = (0, redis_1.createClient)({ url });
await client.connect();
return client;
},
inject: [config_1.ConfigService],
},
redis_service_1.RedisService,
],
exports: ['REDIS_CLIENT', redis_service_1.RedisService],
})
], RedisModule);
//# sourceMappingURL=redis.module.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"redis.module.js","sourceRoot":"","sources":["../../src/common/redis.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAgD;AAChD,2CAA+C;AAC/C,iCAAqC;AACrC,mDAA+C;AAwBxC,IAAM,WAAW,GAAjB,MAAM,WAAW;CAAG,CAAA;AAAd,kCAAW;sBAAX,WAAW;IAjBvB,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC;QACN,SAAS,EAAE;YACT;gBACE,OAAO,EAAE,cAAc;gBACvB,UAAU,EAAE,KAAK,EAAE,aAA4B,EAAE,EAAE;oBACjD,MAAM,GAAG,GAAG,aAAa,CAAC,GAAG,CAAS,WAAW,EAAE,oBAAoB,CAAC,CAAC;oBACzE,MAAM,MAAM,GAAG,IAAA,oBAAY,EAAC,EAAE,GAAG,EAAE,CAAC,CAAC;oBACrC,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;oBACvB,OAAO,MAAM,CAAC;gBAChB,CAAC;gBACD,MAAM,EAAE,CAAC,sBAAa,CAAC;aACxB;YACD,4BAAY;SACb;QACD,OAAO,EAAE,CAAC,cAAc,EAAE,4BAAY,CAAC;KACxC,CAAC;GACW,WAAW,CAAG"}
+10
View File
@@ -0,0 +1,10 @@
import { OnModuleDestroy } from '@nestjs/common';
import type { RedisClientType } from 'redis';
export declare class RedisService implements OnModuleDestroy {
private readonly redisClient;
constructor(redisClient: RedisClientType);
set(key: string, value: any, ttlSeconds?: number): Promise<void>;
get<T>(key: string): Promise<T | null>;
del(key: string): Promise<void>;
onModuleDestroy(): Promise<void>;
}
+55
View File
@@ -0,0 +1,55 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RedisService = void 0;
const common_1 = require("@nestjs/common");
let RedisService = class RedisService {
redisClient;
constructor(redisClient) {
this.redisClient = redisClient;
}
async set(key, value, ttlSeconds) {
const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
if (ttlSeconds) {
await this.redisClient.set(key, stringValue, { EX: ttlSeconds });
}
else {
await this.redisClient.set(key, stringValue);
}
}
async get(key) {
const value = await this.redisClient.get(key);
if (!value)
return null;
try {
return JSON.parse(value);
}
catch {
return value;
}
}
async del(key) {
await this.redisClient.del(key);
}
async onModuleDestroy() {
await this.redisClient.quit();
}
};
exports.RedisService = RedisService;
exports.RedisService = RedisService = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, common_1.Inject)('REDIS_CLIENT')),
__metadata("design:paramtypes", [Object])
], RedisService);
//# sourceMappingURL=redis.service.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"redis.service.js","sourceRoot":"","sources":["../../src/common/redis.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAqE;AAS9D,IAAM,YAAY,GAAlB,MAAM,YAAY;IAEoB;IAD3C,YAC2C,WAA4B;QAA5B,gBAAW,GAAX,WAAW,CAAiB;IACpE,CAAC;IAMJ,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAU,EAAE,UAAmB;QACpD,MAAM,WAAW,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC9E,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;QACnE,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAMD,KAAK,CAAC,GAAG,CAAI,GAAW;QACtB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC9C,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAExB,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAM,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAqB,CAAC;QAC/B,CAAC;IACH,CAAC;IAMD,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;IAChC,CAAC;CACF,CAAA;AA5CY,oCAAY;uBAAZ,YAAY;IADxB,IAAA,mBAAU,GAAE;IAGR,WAAA,IAAA,eAAM,EAAC,cAAc,CAAC,CAAA;;GAFd,YAAY,CA4CxB"}
+11
View File
@@ -0,0 +1,11 @@
export declare class OsmArea {
id: number;
osm_id: number;
longitude: number;
latitude: number;
name: string;
name_ar: string;
place_type: string;
geom: any;
other_tags: string;
}
+65
View File
@@ -0,0 +1,65 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OsmArea = void 0;
const typeorm_1 = require("typeorm");
let OsmArea = class OsmArea {
id;
osm_id;
longitude;
latitude;
name;
name_ar;
place_type;
geom;
other_tags;
};
exports.OsmArea = OsmArea;
__decorate([
(0, typeorm_1.PrimaryGeneratedColumn)(),
__metadata("design:type", Number)
], OsmArea.prototype, "id", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'bigint', nullable: true }),
__metadata("design:type", Number)
], OsmArea.prototype, "osm_id", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'decimal', precision: 11, scale: 8, nullable: true }),
__metadata("design:type", Number)
], OsmArea.prototype, "longitude", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'decimal', precision: 10, scale: 8, nullable: true }),
__metadata("design:type", Number)
], OsmArea.prototype, "latitude", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], OsmArea.prototype, "name", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], OsmArea.prototype, "name_ar", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], OsmArea.prototype, "place_type", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'geometry', nullable: true }),
__metadata("design:type", Object)
], OsmArea.prototype, "geom", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'text', nullable: true }),
__metadata("design:type", String)
], OsmArea.prototype, "other_tags", void 0);
exports.OsmArea = OsmArea = __decorate([
(0, typeorm_1.Entity)('osm_areas')
], OsmArea);
//# sourceMappingURL=osm-area.entity.js.map
@@ -0,0 +1 @@
{"version":3,"file":"osm-area.entity.js","sourceRoot":"","sources":["../../../src/geocoding/entities/osm-area.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAAiE;AAG1D,IAAM,OAAO,GAAb,MAAM,OAAO;IAElB,EAAE,CAAS;IAGX,MAAM,CAAS;IAGf,SAAS,CAAS;IAGlB,QAAQ,CAAS;IAGjB,IAAI,CAAS;IAGb,OAAO,CAAS;IAGhB,UAAU,CAAS;IAGnB,IAAI,CAAM;IAGV,UAAU,CAAS;CACpB,CAAA;AA3BY,0BAAO;AAElB;IADC,IAAA,gCAAsB,GAAE;;mCACd;AAGX;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;uCAC5B;AAGf;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;0CACnD;AAGlB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;yCACpD;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;qCACd;AAGb;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;wCACX;AAGhB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2CACR;AAGnB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;qCACnC;AAGV;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2CACtB;kBA1BR,OAAO;IADnB,IAAA,gBAAM,EAAC,WAAW,CAAC;GACP,OAAO,CA2BnB"}
@@ -0,0 +1,14 @@
export declare class OsmPointWithArea {
osm_id: number;
longitude: number;
latitude: number;
name: string;
name_ar: string;
amenity: string;
shop: string;
addr_street: string;
neighbourhood_name: string;
city_name: string;
other_tags: string;
geom: any;
}
@@ -0,0 +1,80 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OsmPointWithArea = void 0;
const typeorm_1 = require("typeorm");
let OsmPointWithArea = class OsmPointWithArea {
osm_id;
longitude;
latitude;
name;
name_ar;
amenity;
shop;
addr_street;
neighbourhood_name;
city_name;
other_tags;
geom;
};
exports.OsmPointWithArea = OsmPointWithArea;
__decorate([
(0, typeorm_1.PrimaryColumn)({ type: 'bigint' }),
__metadata("design:type", Number)
], OsmPointWithArea.prototype, "osm_id", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'decimal', precision: 11, scale: 8, nullable: true }),
__metadata("design:type", Number)
], OsmPointWithArea.prototype, "longitude", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'decimal', precision: 10, scale: 8, nullable: true }),
__metadata("design:type", Number)
], OsmPointWithArea.prototype, "latitude", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], OsmPointWithArea.prototype, "name", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], OsmPointWithArea.prototype, "name_ar", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], OsmPointWithArea.prototype, "amenity", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], OsmPointWithArea.prototype, "shop", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], OsmPointWithArea.prototype, "addr_street", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], OsmPointWithArea.prototype, "neighbourhood_name", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], OsmPointWithArea.prototype, "city_name", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'text', nullable: true }),
__metadata("design:type", String)
], OsmPointWithArea.prototype, "other_tags", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'geometry', spatialFeatureType: 'Point', nullable: true }),
__metadata("design:type", Object)
], OsmPointWithArea.prototype, "geom", void 0);
exports.OsmPointWithArea = OsmPointWithArea = __decorate([
(0, typeorm_1.Entity)('osm_points_with_area')
], OsmPointWithArea);
//# sourceMappingURL=osm-point-with-area.entity.js.map
@@ -0,0 +1 @@
{"version":3,"file":"osm-point-with-area.entity.js","sourceRoot":"","sources":["../../../src/geocoding/entities/osm-point-with-area.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAAwD;AAGjD,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IAE3B,MAAM,CAAS;IAGf,SAAS,CAAS;IAGlB,QAAQ,CAAS;IAGjB,IAAI,CAAS;IAGb,OAAO,CAAS;IAGhB,OAAO,CAAS;IAGhB,IAAI,CAAS;IAGb,WAAW,CAAS;IAGpB,kBAAkB,CAAS;IAG3B,SAAS,CAAS;IAGlB,UAAU,CAAS;IAGnB,IAAI,CAAM;CACX,CAAA;AApCY,4CAAgB;AAE3B;IADC,IAAA,uBAAa,EAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;;gDACnB;AAGf;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;mDACnD;AAGlB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;kDACpD;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;8CACd;AAGb;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;iDACX;AAGhB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;iDACX;AAGhB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;8CACd;AAGb;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;qDACP;AAGpB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;4DACA;AAG3B;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;mDACT;AAGlB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;oDACtB;AAGnB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,UAAU,EAAE,kBAAkB,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;8CAChE;2BAnCC,gBAAgB;IAD5B,IAAA,gBAAM,EAAC,sBAAsB,CAAC;GAClB,gBAAgB,CAoC5B"}
@@ -0,0 +1,12 @@
export declare class PlaceSyria {
id: number;
latitude: number;
longitude: number;
name: string;
name_ar: string;
name_en: string;
address: string;
category: string;
created_at: Date;
location: any;
}
+70
View File
@@ -0,0 +1,70 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PlaceSyria = void 0;
const typeorm_1 = require("typeorm");
let PlaceSyria = class PlaceSyria {
id;
latitude;
longitude;
name;
name_ar;
name_en;
address;
category;
created_at;
location;
};
exports.PlaceSyria = PlaceSyria;
__decorate([
(0, typeorm_1.PrimaryGeneratedColumn)(),
__metadata("design:type", Number)
], PlaceSyria.prototype, "id", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'decimal', precision: 10, scale: 8, nullable: true }),
__metadata("design:type", Number)
], PlaceSyria.prototype, "latitude", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'decimal', precision: 11, scale: 8, nullable: true }),
__metadata("design:type", Number)
], PlaceSyria.prototype, "longitude", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], PlaceSyria.prototype, "name", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], PlaceSyria.prototype, "name_ar", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], PlaceSyria.prototype, "name_en", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], PlaceSyria.prototype, "address", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], PlaceSyria.prototype, "category", void 0);
__decorate([
(0, typeorm_1.CreateDateColumn)(),
__metadata("design:type", Date)
], PlaceSyria.prototype, "created_at", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'geometry', spatialFeatureType: 'Point', nullable: true }),
__metadata("design:type", Object)
], PlaceSyria.prototype, "location", void 0);
exports.PlaceSyria = PlaceSyria = __decorate([
(0, typeorm_1.Entity)('places_syria')
], PlaceSyria);
//# sourceMappingURL=place-syria.entity.js.map
@@ -0,0 +1 @@
{"version":3,"file":"place-syria.entity.js","sourceRoot":"","sources":["../../../src/geocoding/entities/place-syria.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAAmF;AAG5E,IAAM,UAAU,GAAhB,MAAM,UAAU;IAErB,EAAE,CAAS;IAGX,QAAQ,CAAS;IAGjB,SAAS,CAAS;IAGlB,IAAI,CAAS;IAGb,OAAO,CAAS;IAGhB,OAAO,CAAS;IAGhB,OAAO,CAAS;IAGhB,QAAQ,CAAS;IAGjB,UAAU,CAAO;IAGjB,QAAQ,CAAM;CACf,CAAA;AA9BY,gCAAU;AAErB;IADC,IAAA,gCAAsB,GAAE;;sCACd;AAGX;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;4CACpD;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;6CACnD;AAGlB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;wCACd;AAGb;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2CACX;AAGhB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2CACX;AAGhB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2CACX;AAGhB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;4CACV;AAGjB;IADC,IAAA,0BAAgB,GAAE;8BACP,IAAI;8CAAC;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,UAAU,EAAE,kBAAkB,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;4CAC5D;qBA7BH,UAAU;IADtB,IAAA,gBAAM,EAAC,cAAc,CAAC;GACV,UAAU,CA8BtB"}
+11
View File
@@ -0,0 +1,11 @@
import { GeocodingService } from './geocoding.service';
export declare class GeocodingController {
private readonly geocodingService;
constructor(geocodingService: GeocodingService);
search(query: string): Promise<{
userPlaces: import("./entities/place-syria.entity").PlaceSyria[];
osmAreas: import("./entities/osm-area.entity").OsmArea[];
}>;
reverse(lat: number, lng: number): Promise<any>;
addPlace(placeData: any): Promise<import("./entities/place-syria.entity").PlaceSyria>;
}
+70
View File
@@ -0,0 +1,70 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GeocodingController = void 0;
const common_1 = require("@nestjs/common");
const swagger_1 = require("@nestjs/swagger");
const geocoding_service_1 = require("./geocoding.service");
const api_key_guard_1 = require("../common/guards/api-key.guard");
let GeocodingController = class GeocodingController {
geocodingService;
constructor(geocodingService) {
this.geocodingService = geocodingService;
}
async search(query) {
return this.geocodingService.searchPlaces(query);
}
async reverse(lat, lng) {
return this.geocodingService.reverseGeocode(lat, lng);
}
async addPlace(placeData) {
return this.geocodingService.addPlace(placeData);
}
};
exports.GeocodingController = GeocodingController;
__decorate([
(0, common_1.Get)('search'),
(0, swagger_1.ApiOperation)({ summary: 'Search for locations (Forward Geocoding)' }),
(0, swagger_1.ApiQuery)({ name: 'q', required: true }),
__param(0, (0, common_1.Query)('q')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", Promise)
], GeocodingController.prototype, "search", null);
__decorate([
(0, common_1.Get)('reverse'),
(0, swagger_1.ApiOperation)({ summary: 'Reverse Geocoding (Lat/Lng to Address)' }),
(0, swagger_1.ApiQuery)({ name: 'lat', required: true }),
(0, swagger_1.ApiQuery)({ name: 'lng', required: true }),
__param(0, (0, common_1.Query)('lat')),
__param(1, (0, common_1.Query)('lng')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number, Number]),
__metadata("design:returntype", Promise)
], GeocodingController.prototype, "reverse", null);
__decorate([
(0, common_1.Post)('places'),
(0, swagger_1.ApiOperation)({ summary: 'Add a new location (User Submitted)' }),
__param(0, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], GeocodingController.prototype, "addPlace", null);
exports.GeocodingController = GeocodingController = __decorate([
(0, swagger_1.ApiTags)('geocoding'),
(0, common_1.Controller)('geocoding'),
(0, common_1.UseGuards)(api_key_guard_1.ApiKeyGuard),
__metadata("design:paramtypes", [geocoding_service_1.GeocodingService])
], GeocodingController);
//# sourceMappingURL=geocoding.controller.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"geocoding.controller.js","sourceRoot":"","sources":["../../src/geocoding/geocoding.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA+E;AAC/E,6CAAkE;AAClE,2DAAuD;AACvD,kEAA6D;AAKtD,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IACD;IAA7B,YAA6B,gBAAkC;QAAlC,qBAAgB,GAAhB,gBAAgB,CAAkB;IAAG,CAAC;IAK7D,AAAN,KAAK,CAAC,MAAM,CAAa,KAAa;QACpC,OAAO,IAAI,CAAC,gBAAgB,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IACnD,CAAC;IAMK,AAAN,KAAK,CAAC,OAAO,CAAe,GAAW,EAAgB,GAAW;QAChE,OAAO,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACxD,CAAC;IAIK,AAAN,KAAK,CAAC,QAAQ,CAAS,SAAc;QACnC,OAAO,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IACnD,CAAC;CACF,CAAA;AAvBY,kDAAmB;AAMxB;IAHL,IAAA,YAAG,EAAC,QAAQ,CAAC;IACb,IAAA,sBAAY,EAAC,EAAE,OAAO,EAAE,0CAA0C,EAAE,CAAC;IACrE,IAAA,kBAAQ,EAAC,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,WAAA,IAAA,cAAK,EAAC,GAAG,CAAC,CAAA;;;;iDAEvB;AAMK;IAJL,IAAA,YAAG,EAAC,SAAS,CAAC;IACd,IAAA,sBAAY,EAAC,EAAE,OAAO,EAAE,wCAAwC,EAAE,CAAC;IACnE,IAAA,kBAAQ,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACzC,IAAA,kBAAQ,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3B,WAAA,IAAA,cAAK,EAAC,KAAK,CAAC,CAAA;IAAe,WAAA,IAAA,cAAK,EAAC,KAAK,CAAC,CAAA;;;;kDAErD;AAIK;IAFL,IAAA,aAAI,EAAC,QAAQ,CAAC;IACd,IAAA,sBAAY,EAAC,EAAE,OAAO,EAAE,qCAAqC,EAAE,CAAC;IACjD,WAAA,IAAA,aAAI,GAAE,CAAA;;;;mDAErB;8BAtBU,mBAAmB;IAH/B,IAAA,iBAAO,EAAC,WAAW,CAAC;IACpB,IAAA,mBAAU,EAAC,WAAW,CAAC;IACvB,IAAA,kBAAS,EAAC,2BAAW,CAAC;qCAE0B,oCAAgB;GADpD,mBAAmB,CAuB/B"}
+2
View File
@@ -0,0 +1,2 @@
export declare class GeocodingModule {
}
+33
View File
@@ -0,0 +1,33 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GeocodingModule = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const geocoding_service_1 = require("./geocoding.service");
const geocoding_controller_1 = require("./geocoding.controller");
const place_syria_entity_1 = require("./entities/place-syria.entity");
const osm_area_entity_1 = require("./entities/osm-area.entity");
const osm_point_with_area_entity_1 = require("./entities/osm-point-with-area.entity");
let GeocodingModule = class GeocodingModule {
};
exports.GeocodingModule = GeocodingModule;
exports.GeocodingModule = GeocodingModule = __decorate([
(0, common_1.Module)({
imports: [
typeorm_1.TypeOrmModule.forFeature([
place_syria_entity_1.PlaceSyria,
osm_area_entity_1.OsmArea,
osm_point_with_area_entity_1.OsmPointWithArea
], 'mysqlConnection'),
],
controllers: [geocoding_controller_1.GeocodingController],
providers: [geocoding_service_1.GeocodingService],
})
], GeocodingModule);
//# sourceMappingURL=geocoding.module.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"geocoding.module.js","sourceRoot":"","sources":["../../src/geocoding/geocoding.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,6CAAgD;AAChD,2DAAuD;AACvD,iEAA6D;AAC7D,sEAA2D;AAC3D,gEAAqD;AACrD,sFAAyE;AAalE,IAAM,eAAe,GAArB,MAAM,eAAe;CAAG,CAAA;AAAlB,0CAAe;0BAAf,eAAe;IAX3B,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,uBAAa,CAAC,UAAU,CAAC;gBACvB,+BAAU;gBACV,yBAAO;gBACP,6CAAgB;aACjB,EAAE,iBAAiB,CAAC;SACtB;QACD,WAAW,EAAE,CAAC,0CAAmB,CAAC;QAClC,SAAS,EAAE,CAAC,oCAAgB,CAAC;KAC9B,CAAC;GACW,eAAe,CAAG"}
+17
View File
@@ -0,0 +1,17 @@
import { Repository } from 'typeorm';
import { PlaceSyria } from './entities/place-syria.entity';
import { OsmArea } from './entities/osm-area.entity';
import { OsmPointWithArea } from './entities/osm-point-with-area.entity';
export declare class GeocodingService {
private placesRepository;
private osmAreasRepository;
private osmPointsRepository;
private readonly logger;
constructor(placesRepository: Repository<PlaceSyria>, osmAreasRepository: Repository<OsmArea>, osmPointsRepository: Repository<OsmPointWithArea>);
searchPlaces(query: string): Promise<{
userPlaces: PlaceSyria[];
osmAreas: OsmArea[];
}>;
reverseGeocode(lat: number, lng: number): Promise<any>;
addPlace(data: Partial<PlaceSyria>): Promise<PlaceSyria>;
}
+90
View File
@@ -0,0 +1,90 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var GeocodingService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.GeocodingService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const place_syria_entity_1 = require("./entities/place-syria.entity");
const osm_area_entity_1 = require("./entities/osm-area.entity");
const osm_point_with_area_entity_1 = require("./entities/osm-point-with-area.entity");
let GeocodingService = GeocodingService_1 = class GeocodingService {
placesRepository;
osmAreasRepository;
osmPointsRepository;
logger = new common_1.Logger(GeocodingService_1.name);
constructor(placesRepository, osmAreasRepository, osmPointsRepository) {
this.placesRepository = placesRepository;
this.osmAreasRepository = osmAreasRepository;
this.osmPointsRepository = osmPointsRepository;
}
async searchPlaces(query) {
if (!query || query.length < 3)
return { userPlaces: [], osmAreas: [] };
const userPlaces = await this.placesRepository.find({
where: [
{ name: (0, typeorm_2.Like)(`%${query}%`) },
{ name_ar: (0, typeorm_2.Like)(`%${query}%`) },
],
take: 10,
});
const osmAreas = await this.osmAreasRepository.find({
where: [
{ name: (0, typeorm_2.Like)(`%${query}%`) },
{ name_ar: (0, typeorm_2.Like)(`%${query}%`) },
],
take: 10,
});
return {
userPlaces,
osmAreas,
};
}
async reverseGeocode(lat, lng) {
try {
const query = `
SELECT *, ST_Distance_Sphere(location, POINT(?, ?)) as distance
FROM places_syria
WHERE location IS NOT NULL
ORDER BY distance ASC
LIMIT 5
`;
const results = await this.placesRepository.query(query, [lng, lat]);
return results;
}
catch (error) {
this.logger.error('Reverse geocoding error:', error);
return [];
}
}
async addPlace(data) {
const newPlace = this.placesRepository.create({
...data,
created_at: new Date(),
});
return this.placesRepository.save(newPlace);
}
};
exports.GeocodingService = GeocodingService;
exports.GeocodingService = GeocodingService = GeocodingService_1 = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, typeorm_1.InjectRepository)(place_syria_entity_1.PlaceSyria, 'mysqlConnection')),
__param(1, (0, typeorm_1.InjectRepository)(osm_area_entity_1.OsmArea, 'mysqlConnection')),
__param(2, (0, typeorm_1.InjectRepository)(osm_point_with_area_entity_1.OsmPointWithArea, 'mysqlConnection')),
__metadata("design:paramtypes", [typeorm_2.Repository,
typeorm_2.Repository,
typeorm_2.Repository])
], GeocodingService);
//# sourceMappingURL=geocoding.service.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"geocoding.service.js","sourceRoot":"","sources":["../../src/geocoding/geocoding.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAoD;AACpD,6CAAmD;AACnD,qCAA2C;AAC3C,sEAA2D;AAC3D,gEAAqD;AACrD,sFAAyE;AAGlE,IAAM,gBAAgB,wBAAtB,MAAM,gBAAgB;IAKjB;IAEA;IAEA;IARO,MAAM,GAAG,IAAI,eAAM,CAAC,kBAAgB,CAAC,IAAI,CAAC,CAAC;IAE5D,YAEU,gBAAwC,EAExC,kBAAuC,EAEvC,mBAAiD;QAJjD,qBAAgB,GAAhB,gBAAgB,CAAwB;QAExC,uBAAkB,GAAlB,kBAAkB,CAAqB;QAEvC,wBAAmB,GAAnB,mBAAmB,CAA8B;IACxD,CAAC;IAEJ,KAAK,CAAC,YAAY,CAAC,KAAa;QAC9B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QAGxE,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC;YAClD,KAAK,EAAE;gBACL,EAAE,IAAI,EAAE,IAAA,cAAI,EAAC,IAAI,KAAK,GAAG,CAAC,EAAE;gBAC5B,EAAE,OAAO,EAAE,IAAA,cAAI,EAAC,IAAI,KAAK,GAAG,CAAC,EAAE;aAChC;YACD,IAAI,EAAE,EAAE;SACT,CAAC,CAAC;QAGH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC;YAClD,KAAK,EAAE;gBACL,EAAE,IAAI,EAAE,IAAA,cAAI,EAAC,IAAI,KAAK,GAAG,CAAC,EAAE;gBAC5B,EAAE,OAAO,EAAE,IAAA,cAAI,EAAC,IAAI,KAAK,GAAG,CAAC,EAAE;aAChC;YACD,IAAI,EAAE,EAAE;SACT,CAAC,CAAC;QAEH,OAAO;YACL,UAAU;YACV,QAAQ;SACT,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,GAAW,EAAE,GAAW;QAE3C,IAAI,CAAC;YACH,MAAM,KAAK,GAAG;;;;;;OAMb,CAAC;YACF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;YACrE,OAAO,OAAO,CAAC;QACjB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,EAAE,KAAK,CAAC,CAAC;YACrD,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAyB;QAEtC,MAAM,QAAQ,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC;YAC5C,GAAG,IAAI;YACP,UAAU,EAAE,IAAI,IAAI,EAAE;SACvB,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9C,CAAC;CACF,CAAA;AAjEY,4CAAgB;2BAAhB,gBAAgB;IAD5B,IAAA,mBAAU,GAAE;IAKR,WAAA,IAAA,0BAAgB,EAAC,+BAAU,EAAE,iBAAiB,CAAC,CAAA;IAE/C,WAAA,IAAA,0BAAgB,EAAC,yBAAO,EAAE,iBAAiB,CAAC,CAAA;IAE5C,WAAA,IAAA,0BAAgB,EAAC,6CAAgB,EAAE,iBAAiB,CAAC,CAAA;qCAH5B,oBAAU;QAER,oBAAU;QAET,oBAAU;GAT9B,gBAAgB,CAiE5B"}
+1
View File
@@ -0,0 +1 @@
export {};
+24
View File
@@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@nestjs/core");
const swagger_1 = require("@nestjs/swagger");
const app_module_1 = require("./app.module");
async function bootstrap() {
const app = await core_1.NestFactory.create(app_module_1.AppModule);
app.enableCors();
app.setGlobalPrefix('api');
const config = new swagger_1.DocumentBuilder()
.setTitle('Jordan Map Platform API')
.setDescription('Backend API for map services, routing, and driver telemetry for Jordan')
.setVersion('1.0')
.addTag('maps')
.addTag('telemetry')
.build();
const document = swagger_1.SwaggerModule.createDocument(app, config);
swagger_1.SwaggerModule.setup('docs', app, document);
const port = process.env.API_PORT || 3000;
await app.listen(port);
console.log(`🚀 Jordan Map API is running on: http://localhost:${port}/api`);
}
bootstrap();
//# sourceMappingURL=main.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";;AAAA,uCAA2C;AAC3C,6CAAiE;AACjE,6CAAyC;AAEzC,KAAK,UAAU,SAAS;IACtB,MAAM,GAAG,GAAG,MAAM,kBAAW,CAAC,MAAM,CAAC,sBAAS,CAAC,CAAC;IAIhD,GAAG,CAAC,UAAU,EAAE,CAAC;IAGjB,GAAG,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAG3B,MAAM,MAAM,GAAG,IAAI,yBAAe,EAAE;SACjC,QAAQ,CAAC,yBAAyB,CAAC;SACnC,cAAc,CAAC,wEAAwE,CAAC;SACxF,UAAU,CAAC,KAAK,CAAC;SACjB,MAAM,CAAC,MAAM,CAAC;SACd,MAAM,CAAC,WAAW,CAAC;SACnB,KAAK,EAAE,CAAC;IAEX,MAAM,QAAQ,GAAG,uBAAa,CAAC,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC3D,uBAAa,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;IAE3C,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,IAAI,CAAC;IAC1C,MAAM,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACvB,OAAO,CAAC,GAAG,CAAC,qDAAqD,IAAI,MAAM,CAAC,CAAC;AAC/E,CAAC;AACD,SAAS,EAAE,CAAC"}
+12
View File
@@ -0,0 +1,12 @@
export declare class CandidateRoad {
id: string;
geometry: any;
uniqueDriverCount: number;
totalPoints: number;
averageSpeed: number;
lengthMeters: number;
confidence: number;
status: string;
discoveredAt: Date;
reviewedAt: Date;
}
+76
View File
@@ -0,0 +1,76 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CandidateRoad = void 0;
const typeorm_1 = require("typeorm");
let CandidateRoad = class CandidateRoad {
id;
geometry;
uniqueDriverCount;
totalPoints;
averageSpeed;
lengthMeters;
confidence;
status;
discoveredAt;
reviewedAt;
};
exports.CandidateRoad = CandidateRoad;
__decorate([
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
__metadata("design:type", String)
], CandidateRoad.prototype, "id", void 0);
__decorate([
(0, typeorm_1.Column)({
type: 'geography',
spatialFeatureType: 'LineString',
srid: 4326,
nullable: false,
}),
(0, typeorm_1.Index)({ spatial: true }),
__metadata("design:type", Object)
], CandidateRoad.prototype, "geometry", void 0);
__decorate([
(0, typeorm_1.Column)('int', { default: 0 }),
__metadata("design:type", Number)
], CandidateRoad.prototype, "uniqueDriverCount", void 0);
__decorate([
(0, typeorm_1.Column)('int', { default: 0 }),
__metadata("design:type", Number)
], CandidateRoad.prototype, "totalPoints", void 0);
__decorate([
(0, typeorm_1.Column)('float', { default: 0 }),
__metadata("design:type", Number)
], CandidateRoad.prototype, "averageSpeed", void 0);
__decorate([
(0, typeorm_1.Column)('float', { default: 0 }),
__metadata("design:type", Number)
], CandidateRoad.prototype, "lengthMeters", void 0);
__decorate([
(0, typeorm_1.Column)('float', { default: 0 }),
__metadata("design:type", Number)
], CandidateRoad.prototype, "confidence", void 0);
__decorate([
(0, typeorm_1.Column)({ default: 'pending' }),
__metadata("design:type", String)
], CandidateRoad.prototype, "status", void 0);
__decorate([
(0, typeorm_1.CreateDateColumn)(),
__metadata("design:type", Date)
], CandidateRoad.prototype, "discoveredAt", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'timestamp', nullable: true }),
__metadata("design:type", Date)
], CandidateRoad.prototype, "reviewedAt", void 0);
exports.CandidateRoad = CandidateRoad = __decorate([
(0, typeorm_1.Entity)('candidate_roads')
], CandidateRoad);
//# sourceMappingURL=candidate-road.entity.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"candidate-road.entity.js","sourceRoot":"","sources":["../../src/maps/candidate-road.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAA0F;AASnF,IAAM,aAAa,GAAnB,MAAM,aAAa;IAExB,EAAE,CAAS;IAWX,QAAQ,CAAM;IAKd,iBAAiB,CAAS;IAK1B,WAAW,CAAS;IAKpB,YAAY,CAAS;IAKrB,YAAY,CAAS;IAKrB,UAAU,CAAS;IAKnB,MAAM,CAAS;IAGf,YAAY,CAAO;IAGnB,UAAU,CAAO;CAClB,CAAA;AAlDY,sCAAa;AAExB;IADC,IAAA,gCAAsB,EAAC,MAAM,CAAC;;yCACpB;AAWX;IAPC,IAAA,gBAAM,EAAC;QACN,IAAI,EAAE,WAAW;QACjB,kBAAkB,EAAE,YAAY;QAChC,IAAI,EAAE,IAAI;QACV,QAAQ,EAAE,KAAK;KAChB,CAAC;IACD,IAAA,eAAK,EAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;+CACX;AAKd;IADC,IAAA,gBAAM,EAAC,KAAK,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;wDACJ;AAK1B;IADC,IAAA,gBAAM,EAAC,KAAK,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;kDACV;AAKpB;IADC,IAAA,gBAAM,EAAC,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;mDACX;AAKrB;IADC,IAAA,gBAAM,EAAC,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;mDACX;AAKrB;IADC,IAAA,gBAAM,EAAC,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;iDACb;AAKnB;IADC,IAAA,gBAAM,EAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;;6CAChB;AAGf;IADC,IAAA,0BAAgB,GAAE;8BACL,IAAI;mDAAC;AAGnB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;8BAClC,IAAI;iDAAC;wBAjDN,aAAa;IADzB,IAAA,gBAAM,EAAC,iBAAiB,CAAC;GACb,aAAa,CAkDzB"}
+17
View File
@@ -0,0 +1,17 @@
import { MapsService } from './maps.service';
export declare class MapsController {
private readonly mapsService;
constructor(mapsService: MapsService);
getRoute(fromLat: number, fromLng: number, toLat: number, toLng: number): Promise<{
distance: any;
duration: number;
points: any;
bbox: any;
congestionAware: boolean;
}>;
getConfig(): Promise<{
center: number[];
zoom: number;
tileServerUrl: any;
}>;
}
+57
View File
@@ -0,0 +1,57 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MapsController = void 0;
const common_1 = require("@nestjs/common");
const swagger_1 = require("@nestjs/swagger");
const maps_service_1 = require("./maps.service");
const api_key_guard_1 = require("../common/guards/api-key.guard");
let MapsController = class MapsController {
mapsService;
constructor(mapsService) {
this.mapsService = mapsService;
}
async getRoute(fromLat, fromLng, toLat, toLng) {
return this.mapsService.getRoute([fromLat, fromLng], [toLat, toLng]);
}
async getConfig() {
return this.mapsService.getMapConfig();
}
};
exports.MapsController = MapsController;
__decorate([
(0, common_1.Get)('route'),
(0, swagger_1.ApiOperation)({ summary: 'Calculate a route between two points 🚗' }),
__param(0, (0, common_1.Query)('fromLat')),
__param(1, (0, common_1.Query)('fromLng')),
__param(2, (0, common_1.Query)('toLat')),
__param(3, (0, common_1.Query)('toLng')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number, Number, Number, Number]),
__metadata("design:returntype", Promise)
], MapsController.prototype, "getRoute", null);
__decorate([
(0, common_1.Get)('config'),
(0, swagger_1.ApiOperation)({ summary: 'Get map configuration for the region 🗺️' }),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], MapsController.prototype, "getConfig", null);
exports.MapsController = MapsController = __decorate([
(0, swagger_1.ApiTags)('maps'),
(0, common_1.Controller)('maps'),
(0, common_1.UseGuards)(api_key_guard_1.ApiKeyGuard),
__metadata("design:paramtypes", [maps_service_1.MapsService])
], MapsController);
//# sourceMappingURL=maps.controller.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"maps.controller.js","sourceRoot":"","sources":["../../src/maps/maps.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAmE;AACnE,6CAAwD;AACxD,iDAA6C;AAC7C,kEAA6D;AAKtD,IAAM,cAAc,GAApB,MAAM,cAAc;IACI;IAA7B,YAA6B,WAAwB;QAAxB,gBAAW,GAAX,WAAW,CAAa;IAAG,CAAC;IAInD,AAAN,KAAK,CAAC,QAAQ,CACM,OAAe,EACf,OAAe,EACjB,KAAa,EACb,KAAa;QAE7B,OAAO,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IACvE,CAAC;IAIK,AAAN,KAAK,CAAC,SAAS;QACb,OAAO,IAAI,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC;IACzC,CAAC;CACF,CAAA;AAnBY,wCAAc;AAKnB;IAFL,IAAA,YAAG,EAAC,OAAO,CAAC;IACZ,IAAA,sBAAY,EAAC,EAAE,OAAO,EAAE,yCAAyC,EAAE,CAAC;IAElE,WAAA,IAAA,cAAK,EAAC,SAAS,CAAC,CAAA;IAChB,WAAA,IAAA,cAAK,EAAC,SAAS,CAAC,CAAA;IAChB,WAAA,IAAA,cAAK,EAAC,OAAO,CAAC,CAAA;IACd,WAAA,IAAA,cAAK,EAAC,OAAO,CAAC,CAAA;;;;8CAGhB;AAIK;IAFL,IAAA,YAAG,EAAC,QAAQ,CAAC;IACb,IAAA,sBAAY,EAAC,EAAE,OAAO,EAAE,0CAA0C,EAAE,CAAC;;;;+CAGrE;yBAlBU,cAAc;IAH1B,IAAA,iBAAO,EAAC,MAAM,CAAC;IACf,IAAA,mBAAU,EAAC,MAAM,CAAC;IAClB,IAAA,kBAAS,EAAC,2BAAW,CAAC;qCAEqB,0BAAW;GAD1C,cAAc,CAmB1B"}
+2
View File
@@ -0,0 +1,2 @@
export declare class MapsModule {
}
+31
View File
@@ -0,0 +1,31 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MapsModule = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const maps_service_1 = require("./maps.service");
const maps_controller_1 = require("./maps.controller");
const road_stat_entity_1 = require("./road-stat.entity");
const candidate_road_entity_1 = require("./candidate-road.entity");
const redis_module_1 = require("../common/redis.module");
let MapsModule = class MapsModule {
};
exports.MapsModule = MapsModule;
exports.MapsModule = MapsModule = __decorate([
(0, common_1.Module)({
imports: [
typeorm_1.TypeOrmModule.forFeature([road_stat_entity_1.RoadSegmentStat, candidate_road_entity_1.CandidateRoad]),
redis_module_1.RedisModule,
],
controllers: [maps_controller_1.MapsController],
providers: [maps_service_1.MapsService],
exports: [maps_service_1.MapsService],
})
], MapsModule);
//# sourceMappingURL=maps.module.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"maps.module.js","sourceRoot":"","sources":["../../src/maps/maps.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,6CAAgD;AAChD,iDAA6C;AAC7C,uDAAmD;AACnD,yDAAqD;AACrD,mEAAwD;AACxD,yDAAqD;AAW9C,IAAM,UAAU,GAAhB,MAAM,UAAU;CAAG,CAAA;AAAb,gCAAU;qBAAV,UAAU;IATtB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,uBAAa,CAAC,UAAU,CAAC,CAAC,kCAAe,EAAE,qCAAa,CAAC,CAAC;YAC1D,0BAAW;SACZ;QACD,WAAW,EAAE,CAAC,gCAAc,CAAC;QAC7B,SAAS,EAAE,CAAC,0BAAW,CAAC;QACxB,OAAO,EAAE,CAAC,0BAAW,CAAC;KACvB,CAAC;GACW,UAAU,CAAG"}
+25
View File
@@ -0,0 +1,25 @@
import { ConfigService } from '@nestjs/config';
import { Repository, DataSource } from 'typeorm';
import { RoadSegmentStat } from './road-stat.entity';
import { RedisService } from '../common/redis.service';
export declare class MapsService {
private configService;
private roadStatRepo;
private dataSource;
private redisService;
private readonly graphHopperUrl;
private readonly TRAFFIC_CACHE_KEY;
constructor(configService: ConfigService, roadStatRepo: Repository<RoadSegmentStat>, dataSource: DataSource, redisService: RedisService);
getRoute(from: [number, number], to: [number, number]): Promise<{
distance: any;
duration: number;
points: any;
bbox: any;
congestionAware: boolean;
}>;
getMapConfig(): Promise<{
center: number[];
zoom: number;
tileServerUrl: any;
}>;
}
+134
View File
@@ -0,0 +1,134 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MapsService = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const road_stat_entity_1 = require("./road-stat.entity");
const redis_service_1 = require("../common/redis.service");
const axios_1 = __importDefault(require("axios"));
let MapsService = class MapsService {
configService;
roadStatRepo;
dataSource;
redisService;
graphHopperUrl;
TRAFFIC_CACHE_KEY = 'traffic_snapshot';
constructor(configService, roadStatRepo, dataSource, redisService) {
this.configService = configService;
this.roadStatRepo = roadStatRepo;
this.dataSource = dataSource;
this.redisService = redisService;
this.graphHopperUrl = this.configService.get('GRAPH_HOPPER_URL', 'http://map-routing:8080');
}
async getRoute(from, to) {
const congestedSegments = await this.redisService.get(this.TRAFFIC_CACHE_KEY) || [];
const buffer = 0.05;
const minLat = Math.min(from[0], to[0]) - buffer;
const maxLat = Math.max(from[0], to[0]) + buffer;
const minLng = Math.min(from[1], to[1]) - buffer;
const maxLng = Math.max(from[1], to[1]) + buffer;
const nearbyCongestion = congestedSegments.filter(seg => {
const geom = JSON.parse(typeof seg.geojson === 'string' ? seg.geojson : JSON.stringify(seg.geojson));
return geom.coordinates.some(([lng, lat]) => lat >= minLat && lat <= maxLat && lng >= minLng && lng <= maxLng);
}).slice(0, 50);
const customModel = {
speed: [],
areas: {},
};
nearbyCongestion.forEach((seg, index) => {
const areaId = `cong_${index}`;
const geojson = typeof seg.geojson === 'string' ? JSON.parse(seg.geojson) : seg.geojson;
customModel.areas[areaId] = {
type: 'Feature',
geometry: geojson,
properties: {}
};
customModel.speed.push({
if: `in_${areaId}`,
multiply_by: Math.max(0.1, 1 / seg.congestionFactor)
});
});
try {
const params = {
point: [`${from[0]},${from[1]}`, `${to[0]},${to[1]}`],
profile: 'car',
locale: 'en',
calc_points: true,
points_encoded: true,
elevation: false,
type: 'json',
};
if (customModel.speed.length > 0) {
params.custom_model = JSON.stringify(customModel);
}
const response = await axios_1.default.get(`${this.graphHopperUrl}/route`, {
params,
paramsSerializer: (params) => {
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (Array.isArray(value)) {
value.forEach(v => searchParams.append(key, v));
}
else {
searchParams.append(key, value);
}
});
return searchParams.toString();
},
});
console.log('Routing Successful with congestion:', customModel.speed.length, 'segments');
const route = response.data.paths[0];
if (!route) {
throw new common_1.HttpException('No route found', common_1.HttpStatus.NOT_FOUND);
}
return {
distance: route.distance,
duration: route.time / 1000,
points: route.points,
bbox: route.bbox,
congestionAware: customModel.speed.length > 0,
};
}
catch (error) {
console.error('Routing Error:', error.message);
if (error.response) {
console.error('GraphHopper Response:', error.response.data);
}
throw new common_1.HttpException('Error calculating route', common_1.HttpStatus.BAD_GATEWAY);
}
}
async getMapConfig() {
return {
center: [31.95, 35.91],
zoom: 12,
tileServerUrl: this.configService.get('TILE_SERVER_URL', 'http://localhost:3001'),
};
}
};
exports.MapsService = MapsService;
exports.MapsService = MapsService = __decorate([
(0, common_1.Injectable)(),
__param(1, (0, typeorm_1.InjectRepository)(road_stat_entity_1.RoadSegmentStat)),
__metadata("design:paramtypes", [config_1.ConfigService,
typeorm_2.Repository,
typeorm_2.DataSource,
redis_service_1.RedisService])
], MapsService);
//# sourceMappingURL=maps.service.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"maps.service.js","sourceRoot":"","sources":["../../src/maps/maps.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAAA,2CAAuE;AACvE,2CAA+C;AAC/C,6CAAmD;AACnD,qCAAiD;AACjD,yDAAqD;AACrD,2DAAuD;AACvD,kDAA0B;AAGnB,IAAM,WAAW,GAAjB,MAAM,WAAW;IAKZ;IAEA;IACA;IACA;IARO,cAAc,CAAS;IACvB,iBAAiB,GAAG,kBAAkB,CAAC;IAExD,YACU,aAA4B,EAE5B,YAAyC,EACzC,UAAsB,EACtB,YAA0B;QAJ1B,kBAAa,GAAb,aAAa,CAAe;QAE5B,iBAAY,GAAZ,YAAY,CAA6B;QACzC,eAAU,GAAV,UAAU,CAAY;QACtB,iBAAY,GAAZ,YAAY,CAAc;QAElC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,kBAAkB,EAAE,yBAAyB,CAAC,CAAC;IAC9F,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAsB,EAAE,EAAoB;QAGzD,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAQ,IAAI,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC;QAI3F,MAAM,MAAM,GAAG,IAAI,CAAC;QACpB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;QACjD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;QACjD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;QACjD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;QAEjD,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;YAEtD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;YAErG,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,CAC1C,GAAG,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,CACjE,CAAC;QACJ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAIhB,MAAM,WAAW,GAAQ;YACvB,KAAK,EAAE,EAAE;YACT,KAAK,EAAE,EAAE;SACV,CAAC;QAEF,gBAAgB,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;YACtC,MAAM,MAAM,GAAG,QAAQ,KAAK,EAAE,CAAC;YAC/B,MAAM,OAAO,GAAG,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC;YAExF,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG;gBAC1B,IAAI,EAAE,SAAS;gBACf,QAAQ,EAAE,OAAO;gBACjB,UAAU,EAAE,EAAE;aACf,CAAC;YAEF,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC;gBACrB,EAAE,EAAE,MAAM,MAAM,EAAE;gBAClB,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC,gBAAgB,CAAC;aACrD,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAKH,IAAI,CAAC;YACH,MAAM,MAAM,GAAQ;gBAClB,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACrD,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,IAAI;gBACZ,WAAW,EAAE,IAAI;gBACjB,cAAc,EAAE,IAAI;gBACpB,SAAS,EAAE,KAAK;gBAChB,IAAI,EAAE,MAAM;aACb,CAAC;YAGF,IAAI,WAAW,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACjC,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;YACpD,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,eAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,cAAc,QAAQ,EAAE;gBAC/D,MAAM;gBACN,gBAAgB,EAAE,CAAC,MAAM,EAAE,EAAE;oBAC3B,MAAM,YAAY,GAAG,IAAI,eAAe,EAAE,CAAC;oBAC3C,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;wBAC9C,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;4BACzB,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;wBAClD,CAAC;6BAAM,CAAC;4BACN,YAAY,CAAC,MAAM,CAAC,GAAG,EAAE,KAAY,CAAC,CAAC;wBACzC,CAAC;oBACH,CAAC,CAAC,CAAC;oBACH,OAAO,YAAY,CAAC,QAAQ,EAAE,CAAC;gBACjC,CAAC;aACF,CAAC,CAAC;YAEH,OAAO,CAAC,GAAG,CAAC,qCAAqC,EAAE,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;YAEzF,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACrC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,IAAI,sBAAa,CAAC,gBAAgB,EAAE,mBAAU,CAAC,SAAS,CAAC,CAAC;YAClE,CAAC;YAED,OAAO;gBACL,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,QAAQ,EAAE,KAAK,CAAC,IAAI,GAAG,IAAI;gBAC3B,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,eAAe,EAAE,WAAW,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;aAC9C,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,gBAAgB,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YAC/C,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACnB,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAC9D,CAAC;YACD,MAAM,IAAI,sBAAa,CAAC,yBAAyB,EAAE,mBAAU,CAAC,WAAW,CAAC,CAAC;QAC7E,CAAC;IACH,CAAC;IAED,KAAK,CAAC,YAAY;QAGhB,OAAO;YACL,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC;YACtB,IAAI,EAAE,EAAE;YACR,aAAa,EAAE,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,iBAAiB,EAAE,uBAAuB,CAAC;SAClF,CAAC;IACJ,CAAC;CACF,CAAA;AA7HY,kCAAW;sBAAX,WAAW;IADvB,IAAA,mBAAU,GAAE;IAOR,WAAA,IAAA,0BAAgB,EAAC,kCAAe,CAAC,CAAA;qCADX,sBAAa;QAEd,oBAAU;QACZ,oBAAU;QACR,4BAAY;GATzB,WAAW,CA6HvB"}
+8
View File
@@ -0,0 +1,8 @@
export declare class RoadSegmentStat {
segmentId: string;
averageSpeed: number;
congestionFactor: number;
sampleCount: number;
geometry: any;
lastUpdated: Date;
}
+56
View File
@@ -0,0 +1,56 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RoadSegmentStat = void 0;
const typeorm_1 = require("typeorm");
let RoadSegmentStat = class RoadSegmentStat {
segmentId;
averageSpeed;
congestionFactor;
sampleCount;
geometry;
lastUpdated;
};
exports.RoadSegmentStat = RoadSegmentStat;
__decorate([
(0, typeorm_1.PrimaryColumn)(),
__metadata("design:type", String)
], RoadSegmentStat.prototype, "segmentId", void 0);
__decorate([
(0, typeorm_1.Column)('float', { default: 0 }),
__metadata("design:type", Number)
], RoadSegmentStat.prototype, "averageSpeed", void 0);
__decorate([
(0, typeorm_1.Column)('float', { default: 1.0 }),
__metadata("design:type", Number)
], RoadSegmentStat.prototype, "congestionFactor", void 0);
__decorate([
(0, typeorm_1.Column)('int', { default: 0 }),
__metadata("design:type", Number)
], RoadSegmentStat.prototype, "sampleCount", void 0);
__decorate([
(0, typeorm_1.Column)({
type: 'geography',
spatialFeatureType: 'LineString',
srid: 4326,
nullable: true,
}),
(0, typeorm_1.Index)({ spatial: true }),
__metadata("design:type", Object)
], RoadSegmentStat.prototype, "geometry", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }),
__metadata("design:type", Date)
], RoadSegmentStat.prototype, "lastUpdated", void 0);
exports.RoadSegmentStat = RoadSegmentStat = __decorate([
(0, typeorm_1.Entity)('road_segment_stats')
], RoadSegmentStat);
//# sourceMappingURL=road-stat.entity.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"road-stat.entity.js","sourceRoot":"","sources":["../../src/maps/road-stat.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAA+D;AAGxD,IAAM,eAAe,GAArB,MAAM,eAAe;IAE1B,SAAS,CAAS;IAGlB,YAAY,CAAS;IAGrB,gBAAgB,CAAS;IAGzB,WAAW,CAAS;IASpB,QAAQ,CAAM;IAGd,WAAW,CAAO;CACnB,CAAA;AAxBY,0CAAe;AAE1B;IADC,IAAA,uBAAa,GAAE;;kDACE;AAGlB;IADC,IAAA,gBAAM,EAAC,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;qDACX;AAGrB;IADC,IAAA,gBAAM,EAAC,OAAO,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;;yDACT;AAGzB;IADC,IAAA,gBAAM,EAAC,KAAK,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;oDACV;AASpB;IAPC,IAAA,gBAAM,EAAC;QACN,IAAI,EAAE,WAAW;QACjB,kBAAkB,EAAE,YAAY;QAChC,IAAI,EAAE,IAAI;QACV,QAAQ,EAAE,IAAI;KACf,CAAC;IACD,IAAA,eAAK,EAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;iDACX;AAGd;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,mBAAmB,EAAE,CAAC;8BACrD,IAAI;oDAAC;0BAvBP,eAAe;IAD3B,IAAA,gBAAM,EAAC,oBAAoB,CAAC;GAChB,eAAe,CAwB3B"}
@@ -0,0 +1,9 @@
import { ConfigService } from '@nestjs/config';
export declare class ExternalTelemetryService {
private configService;
private readonly logger;
private readonly baseUrl;
private readonly apiKey;
constructor(configService: ConfigService);
fetchCarTracks(days?: number): Promise<any[]>;
}
+69
View File
@@ -0,0 +1,69 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var ExternalTelemetryService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExternalTelemetryService = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const axios_1 = __importDefault(require("axios"));
let ExternalTelemetryService = ExternalTelemetryService_1 = class ExternalTelemetryService {
configService;
logger = new common_1.Logger(ExternalTelemetryService_1.name);
baseUrl;
apiKey;
constructor(configService) {
this.configService = configService;
this.baseUrl = this.configService.get('LOCATION_SERVER_URL', '');
this.apiKey = this.configService.get('LOCATION_SERVER_API_KEY', '');
}
async fetchCarTracks(days = 1) {
if (!this.baseUrl || !this.apiKey) {
this.logger.error('LOCATION_SERVER_URL or API_KEY not configured in .env');
return [];
}
try {
this.logger.log(`📡 Fetching car tracks from ${this.baseUrl} (Scanning: ${days} days)`);
const response = await axios_1.default.get(`${this.baseUrl}/intaleq/ride/location/getDrirversLocationsTrack.php`, {
params: { days },
headers: {
'X-API-KEY': this.apiKey,
},
timeout: 30000,
});
if (response.data && response.data.success && Array.isArray(response.data.data)) {
this.logger.log(`✅ Successfully fetched ${response.data.data.length} telemetry points.`);
return response.data.data;
}
else {
this.logger.warn('⚠️ Received invalid data structure from location server.');
return [];
}
}
catch (error) {
if (error.response) {
this.logger.error(`❌ Failed to fetch car tracks. Server returned Status ${error.response.status}: ${JSON.stringify(error.response.data)}`);
}
else {
this.logger.error(`❌ Failed to fetch car tracks: ${error.message}`);
}
return [];
}
}
};
exports.ExternalTelemetryService = ExternalTelemetryService;
exports.ExternalTelemetryService = ExternalTelemetryService = ExternalTelemetryService_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [config_1.ConfigService])
], ExternalTelemetryService);
//# sourceMappingURL=external-telemetry.service.js.map
@@ -0,0 +1 @@
{"version":3,"file":"external-telemetry.service.js","sourceRoot":"","sources":["../../src/telemetry/external-telemetry.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAoD;AACpD,2CAA+C;AAC/C,kDAA0B;AASnB,IAAM,wBAAwB,gCAA9B,MAAM,wBAAwB;IAKf;IAJH,MAAM,GAAG,IAAI,eAAM,CAAC,0BAAwB,CAAC,IAAI,CAAC,CAAC;IACnD,OAAO,CAAS;IAChB,MAAM,CAAS;IAEhC,YAAoB,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;QAC9C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAS,qBAAqB,EAAE,EAAE,CAAC,CAAC;QACzE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAS,yBAAyB,EAAE,EAAE,CAAC,CAAC;IAC9E,CAAC;IAMD,KAAK,CAAC,cAAc,CAAC,OAAe,CAAC;QACnC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YAClC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,uDAAuD,CAAC,CAAC;YAC3E,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,IAAI,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,+BAA+B,IAAI,CAAC,OAAO,eAAe,IAAI,QAAQ,CAAC,CAAC;YAExF,MAAM,QAAQ,GAAG,MAAM,eAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,sDAAsD,EAAE;gBACtG,MAAM,EAAE,EAAE,IAAI,EAAE;gBAChB,OAAO,EAAE;oBACP,WAAW,EAAE,IAAI,CAAC,MAAM;iBACzB;gBACD,OAAO,EAAE,KAAK;aACf,CAAC,CAAC;YAEH,IAAI,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChF,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,0BAA0B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,oBAAoB,CAAC,CAAC;gBACzF,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;YAC5B,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC;gBAC7E,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACnB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,wDAAwD,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC7I,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACtE,CAAC;YACD,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;CACF,CAAA;AA/CY,4DAAwB;mCAAxB,wBAAwB;IADpC,IAAA,mBAAU,GAAE;qCAMwB,sBAAa;GALrC,wBAAwB,CA+CpC"}
+45
View File
@@ -0,0 +1,45 @@
import { TelemetryAnalyzerService } from './telemetry-analyzer.service';
export declare class MapRefinementController {
private readonly analyzerService;
constructor(analyzerService: TelemetryAnalyzerService);
getSummary(): Promise<{
telemetry: {
totalPoints: number;
last24h: number;
activeDrivers24h: number;
};
roadSegments: {
analyzed: number;
};
candidateRoads: {
total: number;
pending: number;
approved: number;
rejected: number;
};
}>;
syncExternal(): Promise<{
imported: number;
}>;
refreshCache(): Promise<{
cachedCount: number;
}>;
analyzeRoadSpeeds(hours?: number): Promise<{
segmentsUpdated: number;
totalPointsProcessed: number;
topAdjustments: any[];
}>;
discoverNewRoads(hours?: number): Promise<{
candidatesFound: number;
candidates: any[];
}>;
getCongestionData(north: number, south: number, east: number, west: number): Promise<any[]>;
approveCandidate(id: string): Promise<{
message: string;
status: string;
}>;
rejectCandidate(id: string): Promise<{
message: string;
status: string;
}>;
}
+135
View File
@@ -0,0 +1,135 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MapRefinementController = void 0;
const common_1 = require("@nestjs/common");
const swagger_1 = require("@nestjs/swagger");
const telemetry_analyzer_service_1 = require("./telemetry-analyzer.service");
let MapRefinementController = class MapRefinementController {
analyzerService;
constructor(analyzerService) {
this.analyzerService = analyzerService;
}
async getSummary() {
return this.analyzerService.getAnalysisSummary();
}
async syncExternal() {
return this.analyzerService.syncExternalData();
}
async refreshCache() {
return this.analyzerService.refreshTrafficCache();
}
async analyzeRoadSpeeds(hours) {
const sinceHours = hours ? Number(hours) : 24;
return this.analyzerService.analyzeRoadSpeeds(sinceHours);
}
async discoverNewRoads(hours) {
const sinceHours = hours ? Number(hours) : 168;
return this.analyzerService.discoverNewRoads(sinceHours);
}
async getCongestionData(north, south, east, west) {
if (!north || !south || !east || !west) {
throw new common_1.HttpException('Bounds required: north, south, east, west / يجب تحديد الحدود الجغرافية', common_1.HttpStatus.BAD_REQUEST);
}
return this.analyzerService.getCongestionData({
north: Number(north),
south: Number(south),
east: Number(east),
west: Number(west),
});
}
async approveCandidate(id) {
return { message: `Candidate ${id} approved`, status: 'approved' };
}
async rejectCandidate(id) {
return { message: `Candidate ${id} rejected`, status: 'rejected' };
}
};
exports.MapRefinementController = MapRefinementController;
__decorate([
(0, common_1.Get)('summary'),
(0, swagger_1.ApiOperation)({ summary: 'Get analysis summary 📊 / ملخص التحليل' }),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], MapRefinementController.prototype, "getSummary", null);
__decorate([
(0, common_1.Post)('sync-external'),
(0, swagger_1.ApiOperation)({ summary: 'Sync telemetry from external server 📡 / مزامنة البيانات الخارجية' }),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], MapRefinementController.prototype, "syncExternal", null);
__decorate([
(0, common_1.Post)('refresh-cache'),
(0, swagger_1.ApiOperation)({ summary: 'Refresh Redis traffic snapshot 🚀 / تحديث ذاكرة التخزين' }),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], MapRefinementController.prototype, "refreshCache", null);
__decorate([
(0, common_1.Post)('analyze-speeds'),
(0, swagger_1.ApiOperation)({ summary: 'Run road speed analysis 🚗 / تشغيل تحليل سرعات الطرق' }),
(0, swagger_1.ApiQuery)({ name: 'hours', required: false, description: 'Hours to look back (default: 24)' }),
__param(0, (0, common_1.Query)('hours')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number]),
__metadata("design:returntype", Promise)
], MapRefinementController.prototype, "analyzeRoadSpeeds", null);
__decorate([
(0, common_1.Post)('discover-roads'),
(0, swagger_1.ApiOperation)({ summary: 'Discover new roads from telemetry 🛣️ / اكتشاف طرق جديدة' }),
(0, swagger_1.ApiQuery)({ name: 'hours', required: false, description: 'Hours to look back (default: 168 = 1 week)' }),
__param(0, (0, common_1.Query)('hours')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number]),
__metadata("design:returntype", Promise)
], MapRefinementController.prototype, "discoverNewRoads", null);
__decorate([
(0, common_1.Get)('congestion'),
(0, swagger_1.ApiOperation)({ summary: 'Get congestion data for heatmap 🗺️ / بيانات الازدحام' }),
(0, swagger_1.ApiQuery)({ name: 'north', required: true }),
(0, swagger_1.ApiQuery)({ name: 'south', required: true }),
(0, swagger_1.ApiQuery)({ name: 'east', required: true }),
(0, swagger_1.ApiQuery)({ name: 'west', required: true }),
__param(0, (0, common_1.Query)('north')),
__param(1, (0, common_1.Query)('south')),
__param(2, (0, common_1.Query)('east')),
__param(3, (0, common_1.Query)('west')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number, Number, Number, Number]),
__metadata("design:returntype", Promise)
], MapRefinementController.prototype, "getCongestionData", null);
__decorate([
(0, common_1.Patch)('candidates/:id/approve'),
(0, swagger_1.ApiOperation)({ summary: 'Approve a candidate road ✅ / الموافقة على طريق مرشح' }),
__param(0, (0, common_1.Param)('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", Promise)
], MapRefinementController.prototype, "approveCandidate", null);
__decorate([
(0, common_1.Patch)('candidates/:id/reject'),
(0, swagger_1.ApiOperation)({ summary: 'Reject a candidate road ❌ / رفض طريق مرشح' }),
__param(0, (0, common_1.Param)('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", Promise)
], MapRefinementController.prototype, "rejectCandidate", null);
exports.MapRefinementController = MapRefinementController = __decorate([
(0, swagger_1.ApiTags)('map-refinement'),
(0, common_1.Controller)('map-refinement'),
__metadata("design:paramtypes", [telemetry_analyzer_service_1.TelemetryAnalyzerService])
], MapRefinementController);
//# sourceMappingURL=map-refinement.controller.js.map
@@ -0,0 +1 @@
{"version":3,"file":"map-refinement.controller.js","sourceRoot":"","sources":["../../src/telemetry/map-refinement.controller.ts"],"names":[],"mappings":"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"}
+56
View File
@@ -0,0 +1,56 @@
import { Repository, DataSource } from 'typeorm';
import { TelemetryLog } from './telemetry.entity';
import { RoadSegmentStat } from '../maps/road-stat.entity';
import { CandidateRoad } from '../maps/candidate-road.entity';
import { RedisService } from '../common/redis.service';
import { ExternalTelemetryService } from './external-telemetry.service';
export declare class TelemetryAnalyzerService {
private telemetryRepo;
private roadStatRepo;
private candidateRoadRepo;
private dataSource;
private redisService;
private externalTelemetry;
private readonly logger;
private readonly TRAFFIC_CACHE_KEY;
constructor(telemetryRepo: Repository<TelemetryLog>, roadStatRepo: Repository<RoadSegmentStat>, candidateRoadRepo: Repository<CandidateRoad>, dataSource: DataSource, redisService: RedisService, externalTelemetry: ExternalTelemetryService);
handleNightlyIntelligence(): Promise<void>;
syncExternalData(days?: number): Promise<{
imported: number;
}>;
refreshTrafficCache(): Promise<{
cachedCount: number;
}>;
analyzeRoadSpeeds(sinceHours?: number): Promise<{
segmentsUpdated: number;
totalPointsProcessed: number;
topAdjustments: any[];
}>;
discoverNewRoads(sinceHours?: number): Promise<{
candidatesFound: number;
candidates: any[];
}>;
getCongestionData(bounds: {
north: number;
south: number;
east: number;
west: number;
}): Promise<any[]>;
getAnalysisSummary(): Promise<{
telemetry: {
totalPoints: number;
last24h: number;
activeDrivers24h: number;
};
roadSegments: {
analyzed: number;
};
candidateRoads: {
total: number;
pending: number;
approved: number;
rejected: number;
};
}>;
private calculateConfidence;
}
+333
View File
@@ -0,0 +1,333 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var TelemetryAnalyzerService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TelemetryAnalyzerService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const schedule_1 = require("@nestjs/schedule");
const telemetry_entity_1 = require("./telemetry.entity");
const road_stat_entity_1 = require("../maps/road-stat.entity");
const candidate_road_entity_1 = require("../maps/candidate-road.entity");
const redis_service_1 = require("../common/redis.service");
const external_telemetry_service_1 = require("./external-telemetry.service");
let TelemetryAnalyzerService = TelemetryAnalyzerService_1 = class TelemetryAnalyzerService {
telemetryRepo;
roadStatRepo;
candidateRoadRepo;
dataSource;
redisService;
externalTelemetry;
logger = new common_1.Logger(TelemetryAnalyzerService_1.name);
TRAFFIC_CACHE_KEY = 'traffic_snapshot';
constructor(telemetryRepo, roadStatRepo, candidateRoadRepo, dataSource, redisService, externalTelemetry) {
this.telemetryRepo = telemetryRepo;
this.roadStatRepo = roadStatRepo;
this.candidateRoadRepo = candidateRoadRepo;
this.dataSource = dataSource;
this.redisService = redisService;
this.externalTelemetry = externalTelemetry;
}
async handleNightlyIntelligence() {
this.logger.log('⏰ Starting automated 3 AM intelligence process...');
try {
await this.syncExternalData(1);
await this.analyzeRoadSpeeds(24);
await this.discoverNewRoads(168);
await this.refreshTrafficCache();
this.logger.log('✅ 3 AM intelligence process complete.');
}
catch (error) {
this.logger.error('❌ Nightly intelligence failed:', error.stack);
}
}
async syncExternalData(days = 1) {
this.logger.log(`📡 Starting batch sync (Window: ${days} days)...`);
const tracks = await this.externalTelemetry.fetchCarTracks(days);
if (!tracks || tracks.length === 0) {
this.logger.warn(`⚠️ No tracks found on external server for the last ${days} days.`);
return { imported: 0 };
}
this.logger.log(`✅ Received ${tracks.length} tracks. Commencing batch insertion...`);
this.logger.log(`📥 Saving ${tracks.length} points to database...`);
const entities = tracks.map(t => ({
driverId: t.driver_id,
latitude: t.latitude,
longitude: t.longitude,
speed: t.speed,
heading: t.heading,
timestamp: new Date(t.created_at || t.timestamp),
location: {
type: 'Point',
coordinates: [t.longitude, t.latitude],
},
}));
const CHUNK_SIZE = 1000;
for (let i = 0; i < entities.length; i += CHUNK_SIZE) {
const chunk = entities.slice(i, i + CHUNK_SIZE);
const logs = this.telemetryRepo.create(chunk);
await this.telemetryRepo.save(logs);
}
return { imported: tracks.length };
}
async refreshTrafficCache() {
this.logger.log('🚀 Refreshing Redis traffic snapshot...');
const congested = await this.roadStatRepo.query(`
SELECT
"segmentId",
"congestionFactor",
ST_AsGeoJSON(geometry) as geojson
FROM road_segment_stats
WHERE "congestionFactor" > 1.1
`);
if (congested.length === 0) {
await this.redisService.del(this.TRAFFIC_CACHE_KEY);
return { cachedCount: 0 };
}
await this.redisService.set(this.TRAFFIC_CACHE_KEY, congested);
this.logger.log(`✅ Cached ${congested.length} congested segments in Redis.`);
return { cachedCount: congested.length };
}
async analyzeRoadSpeeds(sinceHours = 24) {
this.logger.log(`🔍 Starting road speed analysis for last ${sinceHours}h...`);
const matchedData = await this.dataSource.query(`
WITH matched_points AS (
SELECT
t.id AS telemetry_id,
t.speed,
t."driverId",
l.osm_id,
l.name,
l.highway,
l.way_4326,
ST_Distance(t.location::geography, l.way_4326::geography) AS distance_m
FROM telemetry_logs t
CROSS JOIN LATERAL (
SELECT osm_id, name, highway, ST_Transform(way, 4326) AS way_4326
FROM planet_osm_line
WHERE highway IS NOT NULL
ORDER BY way <-> ST_Transform(t.location::geometry, 3857)
LIMIT 1
) l
WHERE t.timestamp >= NOW() - INTERVAL '${sinceHours} hours'
AND t.speed > 2 -- Ignore stationary points / تجاهل النقاط الثابتة
AND ST_Distance(t.location::geography, l.way_4326::geography) < 15 -- 15m snap threshold
)
SELECT
osm_id::text AS segment_id,
name,
highway,
AVG(speed) AS avg_speed,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY speed) AS median_speed,
COUNT(*) AS sample_count,
COUNT(DISTINCT "driverId") AS unique_drivers,
ST_AsGeoJSON(MIN(way_4326)) AS geojson
FROM matched_points
GROUP BY osm_id, name, highway
HAVING COUNT(*) >= 3 -- Minimum 3 samples for reliability / 3 عينات على الأقل
ORDER BY sample_count DESC
`);
let segmentsUpdated = 0;
let totalPoints = 0;
for (const row of matchedData) {
const medianSpeed = parseFloat(row.median_speed);
const sampleCount = parseInt(row.sample_count);
const geojson = JSON.parse(row.geojson);
totalPoints += sampleCount;
let congestionFactor = 1.0;
const majorRoadTypes = ['primary', 'secondary', 'trunk', 'motorway'];
if (majorRoadTypes.includes(row.highway) && medianSpeed < 30) {
congestionFactor = 30 / Math.max(medianSpeed, 1);
}
await this.roadStatRepo.upsert({
segmentId: row.segment_id,
averageSpeed: medianSpeed,
congestionFactor,
sampleCount,
lastUpdated: new Date(),
geometry: geojson,
}, ['segmentId']);
segmentsUpdated++;
}
return {
segmentsUpdated,
totalPointsProcessed: totalPoints,
topAdjustments: matchedData.slice(0, 10).map(d => ({
segmentId: d.segment_id,
name: d.name || 'Unnamed Road',
speed: Math.round(parseFloat(d.median_speed)),
samples: parseInt(d.sample_count)
}))
};
}
async discoverNewRoads(sinceHours = 168) {
this.logger.log(`🛣️ Starting road discovery for last ${sinceHours}h (${sinceHours / 24}d)...`);
const candidates = await this.dataSource.query(`
WITH off_road_points AS (
SELECT
t.id,
t."driverId",
t.speed,
t.heading,
t.location,
t.timestamp
FROM telemetry_logs t
WHERE t.timestamp >= NOW() - INTERVAL '${sinceHours} hours'
AND t.speed > 5 -- Moving, not parked / متحرك وليس متوقف
AND NOT EXISTS (
SELECT 1
FROM planet_osm_line l
WHERE l.highway IS NOT NULL
AND ST_DWithin(t.location::geography, ST_Transform(l.way, 4326)::geography, 15)
)
),
-- Step 2: Cluster nearby off-road points using DBSCAN
-- الخطوة 2: تجميع النقاط القريبة باستخدام DBSCAN
clustered AS (
SELECT
*,
ST_ClusterDBSCAN(location::geometry, eps := 0.0003, minpoints := 5)
OVER () AS cluster_id
FROM off_road_points
)
-- Step 3: Aggregate clusters into candidate road lines
-- الخطوة 3: تحويل التجمعات إلى خطوط طرق مرشحة
SELECT
cluster_id,
COUNT(*) AS total_points,
COUNT(DISTINCT "driverId") AS unique_drivers,
AVG(speed) AS avg_speed,
ST_AsGeoJSON(ST_MakeLine(location::geometry ORDER BY timestamp)) AS geojson_line,
ST_Length(ST_MakeLine(location::geometry ORDER BY timestamp)::geography) AS length_m
FROM clustered
WHERE cluster_id IS NOT NULL
GROUP BY cluster_id
HAVING COUNT(DISTINCT "driverId") >= 2 -- At least 2 drivers / سائقان على الأقل
AND COUNT(*) >= 10 -- At least 10 points / 10 نقاط على الأقل
ORDER BY unique_drivers DESC, total_points DESC
`);
const savedCandidates = [];
for (const c of candidates) {
const geojson = JSON.parse(c.geojson_line);
const confidence = this.calculateConfidence(parseInt(c.unique_drivers), parseInt(c.total_points), parseFloat(c.length_m));
const candidate = this.candidateRoadRepo.create({
geometry: geojson,
uniqueDriverCount: parseInt(c.unique_drivers),
totalPoints: parseInt(c.total_points),
averageSpeed: parseFloat(c.avg_speed),
lengthMeters: parseFloat(c.length_m),
confidence,
status: 'pending',
});
const saved = await this.candidateRoadRepo.save(candidate);
savedCandidates.push({
id: saved.id,
uniqueDrivers: saved.uniqueDriverCount,
totalPoints: saved.totalPoints,
averageSpeed: Math.round(saved.averageSpeed * 10) / 10,
lengthMeters: Math.round(saved.lengthMeters),
confidence: Math.round(saved.confidence * 100) / 100,
geojson,
});
}
this.logger.log(`✅ Road discovery complete: ${savedCandidates.length} candidates found`);
return {
candidatesFound: savedCandidates.length,
candidates: savedCandidates,
};
}
async getCongestionData(bounds) {
return this.dataSource.query(`
SELECT
rs."segmentId" AS segment_id,
rs."averageSpeed" AS avg_speed,
rs."congestionFactor" AS congestion_factor,
rs."sampleCount" AS sample_count,
ST_AsGeoJSON(rs.geometry) AS geojson
FROM road_segment_stats rs
WHERE rs.geometry IS NOT NULL
AND ST_Intersects(
rs.geometry::geometry,
ST_MakeEnvelope($1, $2, $3, $4, 4326)
)
AND rs."sampleCount" >= 3
ORDER BY rs."congestionFactor" DESC
`, [bounds.west, bounds.south, bounds.east, bounds.north]);
}
async getAnalysisSummary() {
const [telemetryCount] = await this.dataSource.query(`SELECT COUNT(*) as count FROM telemetry_logs`);
const [roadStatCount] = await this.dataSource.query(`SELECT COUNT(*) as count FROM road_segment_stats`);
const [candidateCount] = await this.dataSource.query(`SELECT COUNT(*) as count,
COUNT(*) FILTER (WHERE status = 'pending') as pending,
COUNT(*) FILTER (WHERE status = 'approved') as approved,
COUNT(*) FILTER (WHERE status = 'rejected') as rejected
FROM candidate_roads`);
const [recentActivity] = await this.dataSource.query(`
SELECT
COUNT(*) as points_last_24h,
COUNT(DISTINCT "driverId") as active_drivers_24h
FROM telemetry_logs
WHERE timestamp >= NOW() - INTERVAL '24 hours'
`);
return {
telemetry: {
totalPoints: parseInt(telemetryCount?.count || '0'),
last24h: parseInt(recentActivity?.points_last_24h || '0'),
activeDrivers24h: parseInt(recentActivity?.active_drivers_24h || '0'),
},
roadSegments: {
analyzed: parseInt(roadStatCount?.count || '0'),
},
candidateRoads: {
total: parseInt(candidateCount?.count || '0'),
pending: parseInt(candidateCount?.pending || '0'),
approved: parseInt(candidateCount?.approved || '0'),
rejected: parseInt(candidateCount?.rejected || '0'),
},
};
}
calculateConfidence(uniqueDrivers, totalPoints, lengthMeters) {
const driverScore = Math.min(uniqueDrivers / 5, 1.0) * 0.4;
const pointScore = Math.min(totalPoints / 50, 1.0) * 0.3;
let lengthScore = 0;
if (lengthMeters >= 50 && lengthMeters <= 2000) {
lengthScore = 0.3;
}
else if (lengthMeters > 2000) {
lengthScore = 0.2;
}
return Math.round((driverScore + pointScore + lengthScore) * 100) / 100;
}
};
exports.TelemetryAnalyzerService = TelemetryAnalyzerService;
__decorate([
(0, schedule_1.Cron)('0 3 * * *'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], TelemetryAnalyzerService.prototype, "handleNightlyIntelligence", null);
exports.TelemetryAnalyzerService = TelemetryAnalyzerService = TelemetryAnalyzerService_1 = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, typeorm_1.InjectRepository)(telemetry_entity_1.TelemetryLog)),
__param(1, (0, typeorm_1.InjectRepository)(road_stat_entity_1.RoadSegmentStat)),
__param(2, (0, typeorm_1.InjectRepository)(candidate_road_entity_1.CandidateRoad)),
__metadata("design:paramtypes", [typeorm_2.Repository,
typeorm_2.Repository,
typeorm_2.Repository,
typeorm_2.DataSource,
redis_service_1.RedisService,
external_telemetry_service_1.ExternalTelemetryService])
], TelemetryAnalyzerService);
//# sourceMappingURL=telemetry-analyzer.service.js.map
File diff suppressed because one or more lines are too long
+77
View File
@@ -0,0 +1,77 @@
import { TelemetryService, type DriverTelemetryDto } from './telemetry.service';
import { TelemetryAnalyzerService } from './telemetry-analyzer.service';
export declare class TelemetryController {
private readonly telemetryService;
private readonly analyzerService;
constructor(telemetryService: TelemetryService, analyzerService: TelemetryAnalyzerService);
ingest(data: DriverTelemetryDto): Promise<{
success: boolean;
timestamp: Date;
}>;
sync(): Promise<{
imported: number;
}>;
analyze(hours?: number): Promise<{
success: boolean;
roadAnalysis: {
segmentsUpdated: number;
totalPointsProcessed: number;
topAdjustments: any[];
};
roadDiscovery: {
candidatesFound: number;
candidates: any[];
};
}>;
processIntelligence(hours?: number): Promise<{
success: boolean;
timestamp: Date;
sync: {
imported: number;
};
analysis: {
roads: {
segmentsUpdated: number;
totalPointsProcessed: number;
topAdjustments: any[];
};
discovery: {
candidatesFound: number;
candidates: any[];
};
};
currentStatus: {
telemetry: {
totalPoints: number;
last24h: number;
activeDrivers24h: number;
};
roadSegments: {
analyzed: number;
};
candidateRoads: {
total: number;
pending: number;
approved: number;
rejected: number;
};
};
}>;
getSummary(): Promise<{
telemetry: {
totalPoints: number;
last24h: number;
activeDrivers24h: number;
};
roadSegments: {
analyzed: number;
};
candidateRoads: {
total: number;
pending: number;
approved: number;
rejected: number;
};
}>;
getNearby(lat: number, lng: number, radius: number): Promise<import("./telemetry.entity").TelemetryLog[]>;
}
+120
View File
@@ -0,0 +1,120 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TelemetryController = void 0;
const common_1 = require("@nestjs/common");
const swagger_1 = require("@nestjs/swagger");
const telemetry_service_1 = require("./telemetry.service");
const telemetry_analyzer_service_1 = require("./telemetry-analyzer.service");
let TelemetryController = class TelemetryController {
telemetryService;
analyzerService;
constructor(telemetryService, analyzerService) {
this.telemetryService = telemetryService;
this.analyzerService = analyzerService;
}
async ingest(data) {
return this.telemetryService.ingest(data);
}
async sync() {
return this.analyzerService.syncExternalData();
}
async analyze(hours) {
const roadResults = await this.analyzerService.analyzeRoadSpeeds(hours || 24);
const discoveryResults = await this.analyzerService.discoverNewRoads(hours || 168);
return {
success: true,
roadAnalysis: roadResults,
roadDiscovery: discoveryResults,
};
}
async processIntelligence(hours) {
const syncResults = await this.analyzerService.syncExternalData();
const roadResults = await this.analyzerService.analyzeRoadSpeeds(hours || 24);
const discoveryResults = await this.analyzerService.discoverNewRoads(hours || 168);
const summary = await this.analyzerService.getAnalysisSummary();
return {
success: true,
timestamp: new Date(),
sync: syncResults,
analysis: {
roads: roadResults,
discovery: discoveryResults,
},
currentStatus: summary,
};
}
async getSummary() {
return this.analyzerService.getAnalysisSummary();
}
async getNearby(lat, lng, radius) {
return this.telemetryService.getRecentDrivers(Number(lat), Number(lng), Number(radius));
}
};
exports.TelemetryController = TelemetryController;
__decorate([
(0, common_1.Post)(),
(0, swagger_1.ApiOperation)({ summary: 'Ingest driver telemetry 📡' }),
__param(0, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], TelemetryController.prototype, "ingest", null);
__decorate([
(0, common_1.Post)('sync'),
(0, swagger_1.ApiOperation)({ summary: 'Sync data from external server 📡' }),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], TelemetryController.prototype, "sync", null);
__decorate([
(0, common_1.Post)('analyze'),
(0, swagger_1.ApiOperation)({ summary: 'Trigger road and speed analysis 🧠' }),
__param(0, (0, common_1.Query)('hours')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number]),
__metadata("design:returntype", Promise)
], TelemetryController.prototype, "analyze", null);
__decorate([
(0, common_1.Post)('process-intelligence'),
(0, swagger_1.ApiOperation)({ summary: 'Automated One-Click Sync & Analyze 🚀' }),
__param(0, (0, common_1.Query)('hours')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number]),
__metadata("design:returntype", Promise)
], TelemetryController.prototype, "processIntelligence", null);
__decorate([
(0, common_1.Get)('summary'),
(0, swagger_1.ApiOperation)({ summary: 'Get analysis summary 📊' }),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], TelemetryController.prototype, "getSummary", null);
__decorate([
(0, common_1.Get)('nearby'),
(0, swagger_1.ApiOperation)({ summary: 'Find nearby drivers 📍' }),
__param(0, (0, common_1.Query)('lat')),
__param(1, (0, common_1.Query)('lng')),
__param(2, (0, common_1.Query)('radius')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number, Number, Number]),
__metadata("design:returntype", Promise)
], TelemetryController.prototype, "getNearby", null);
exports.TelemetryController = TelemetryController = __decorate([
(0, swagger_1.ApiTags)('telemetry'),
(0, common_1.Controller)('telemetry'),
__metadata("design:paramtypes", [telemetry_service_1.TelemetryService,
telemetry_analyzer_service_1.TelemetryAnalyzerService])
], TelemetryController);
//# sourceMappingURL=telemetry.controller.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"telemetry.controller.js","sourceRoot":"","sources":["../../src/telemetry/telemetry.controller.ts"],"names":[],"mappings":"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"}
+10
View File
@@ -0,0 +1,10 @@
export declare class TelemetryLog {
id: number;
driverId: string;
latitude: number;
longitude: number;
speed: number;
heading: number;
timestamp: Date;
location: any;
}
+68
View File
@@ -0,0 +1,68 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TelemetryLog = void 0;
const typeorm_1 = require("typeorm");
let TelemetryLog = class TelemetryLog {
id;
driverId;
latitude;
longitude;
speed;
heading;
timestamp;
location;
};
exports.TelemetryLog = TelemetryLog;
__decorate([
(0, typeorm_1.PrimaryGeneratedColumn)(),
__metadata("design:type", Number)
], TelemetryLog.prototype, "id", void 0);
__decorate([
(0, typeorm_1.Column)(),
(0, typeorm_1.Index)(),
__metadata("design:type", String)
], TelemetryLog.prototype, "driverId", void 0);
__decorate([
(0, typeorm_1.Column)('decimal', { precision: 10, scale: 7 }),
__metadata("design:type", Number)
], TelemetryLog.prototype, "latitude", void 0);
__decorate([
(0, typeorm_1.Column)('decimal', { precision: 10, scale: 7 }),
__metadata("design:type", Number)
], TelemetryLog.prototype, "longitude", void 0);
__decorate([
(0, typeorm_1.Column)('float'),
__metadata("design:type", Number)
], TelemetryLog.prototype, "speed", void 0);
__decorate([
(0, typeorm_1.Column)('float'),
__metadata("design:type", Number)
], TelemetryLog.prototype, "heading", void 0);
__decorate([
(0, typeorm_1.CreateDateColumn)(),
(0, typeorm_1.Index)(),
__metadata("design:type", Date)
], TelemetryLog.prototype, "timestamp", void 0);
__decorate([
(0, typeorm_1.Column)({
type: 'geography',
spatialFeatureType: 'Point',
srid: 4326,
nullable: true,
}),
(0, typeorm_1.Index)({ spatial: true }),
__metadata("design:type", Object)
], TelemetryLog.prototype, "location", void 0);
exports.TelemetryLog = TelemetryLog = __decorate([
(0, typeorm_1.Entity)('telemetry_logs')
], TelemetryLog);
//# sourceMappingURL=telemetry.entity.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"telemetry.entity.js","sourceRoot":"","sources":["../../src/telemetry/telemetry.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAA0F;AAGnF,IAAM,YAAY,GAAlB,MAAM,YAAY;IAEvB,EAAE,CAAS;IAIX,QAAQ,CAAS;IAGjB,QAAQ,CAAS;IAGjB,SAAS,CAAS;IAGlB,KAAK,CAAS;IAGd,OAAO,CAAS;IAIhB,SAAS,CAAO;IAWhB,QAAQ,CAAM;CACf,CAAA;AAlCY,oCAAY;AAEvB;IADC,IAAA,gCAAsB,GAAE;;wCACd;AAIX;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,eAAK,GAAE;;8CACS;AAGjB;IADC,IAAA,gBAAM,EAAC,SAAS,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;;8CAC9B;AAGjB;IADC,IAAA,gBAAM,EAAC,SAAS,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;;+CAC7B;AAGlB;IADC,IAAA,gBAAM,EAAC,OAAO,CAAC;;2CACF;AAGd;IADC,IAAA,gBAAM,EAAC,OAAO,CAAC;;6CACA;AAIhB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,eAAK,GAAE;8BACG,IAAI;+CAAC;AAWhB;IAPC,IAAA,gBAAM,EAAC;QACN,IAAI,EAAE,WAAW;QACjB,kBAAkB,EAAE,OAAO;QAC3B,IAAI,EAAE,IAAI;QACV,QAAQ,EAAE,IAAI;KACf,CAAC;IACD,IAAA,eAAK,EAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;8CACX;uBAjCH,YAAY;IADxB,IAAA,gBAAM,EAAC,gBAAgB,CAAC;GACZ,YAAY,CAkCxB"}
+2
View File
@@ -0,0 +1,2 @@
export declare class TelemetryModule {
}
+36
View File
@@ -0,0 +1,36 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TelemetryModule = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const telemetry_entity_1 = require("./telemetry.entity");
const telemetry_service_1 = require("./telemetry.service");
const telemetry_controller_1 = require("./telemetry.controller");
const telemetry_analyzer_service_1 = require("./telemetry-analyzer.service");
const map_refinement_controller_1 = require("./map-refinement.controller");
const road_stat_entity_1 = require("../maps/road-stat.entity");
const candidate_road_entity_1 = require("../maps/candidate-road.entity");
const redis_module_1 = require("../common/redis.module");
const redis_service_1 = require("../common/redis.service");
const external_telemetry_service_1 = require("./external-telemetry.service");
let TelemetryModule = class TelemetryModule {
};
exports.TelemetryModule = TelemetryModule;
exports.TelemetryModule = TelemetryModule = __decorate([
(0, common_1.Module)({
imports: [
typeorm_1.TypeOrmModule.forFeature([telemetry_entity_1.TelemetryLog, road_stat_entity_1.RoadSegmentStat, candidate_road_entity_1.CandidateRoad]),
redis_module_1.RedisModule,
],
controllers: [telemetry_controller_1.TelemetryController, map_refinement_controller_1.MapRefinementController],
providers: [telemetry_service_1.TelemetryService, telemetry_analyzer_service_1.TelemetryAnalyzerService, redis_service_1.RedisService, external_telemetry_service_1.ExternalTelemetryService],
exports: [telemetry_service_1.TelemetryService, telemetry_analyzer_service_1.TelemetryAnalyzerService, redis_service_1.RedisService, external_telemetry_service_1.ExternalTelemetryService],
})
], TelemetryModule);
//# sourceMappingURL=telemetry.module.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"telemetry.module.js","sourceRoot":"","sources":["../../src/telemetry/telemetry.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,6CAAgD;AAChD,yDAAkD;AAClD,2DAAuD;AACvD,iEAA6D;AAC7D,6EAAwE;AACxE,2EAAsE;AACtE,+DAA2D;AAC3D,yEAA8D;AAC9D,yDAAqD;AACrD,2DAAuD;AAEvD,6EAAwE;AAWjE,IAAM,eAAe,GAArB,MAAM,eAAe;CAAG,CAAA;AAAlB,0CAAe;0BAAf,eAAe;IAT3B,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,uBAAa,CAAC,UAAU,CAAC,CAAC,+BAAY,EAAE,kCAAe,EAAE,qCAAa,CAAC,CAAC;YACxE,0BAAW;SACZ;QACD,WAAW,EAAE,CAAC,0CAAmB,EAAE,mDAAuB,CAAC;QAC3D,SAAS,EAAE,CAAC,oCAAgB,EAAE,qDAAwB,EAAE,4BAAY,EAAE,qDAAwB,CAAC;QAC/F,OAAO,EAAE,CAAC,oCAAgB,EAAE,qDAAwB,EAAE,4BAAY,EAAE,qDAAwB,CAAC;KAC9F,CAAC;GACW,eAAe,CAAG"}
+19
View File
@@ -0,0 +1,19 @@
import { Repository } from 'typeorm';
import { TelemetryLog } from './telemetry.entity';
export interface DriverTelemetryDto {
driver_id: string;
latitude: number;
longitude: number;
speed: number;
heading: number;
}
export declare class TelemetryService {
private telemetryRepository;
private readonly logger;
constructor(telemetryRepository: Repository<TelemetryLog>);
ingest(data: DriverTelemetryDto): Promise<{
success: boolean;
timestamp: Date;
}>;
getRecentDrivers(lat: number, lng: number, radiusMeters?: number): Promise<TelemetryLog[]>;
}
+59
View File
@@ -0,0 +1,59 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var TelemetryService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TelemetryService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const telemetry_entity_1 = require("./telemetry.entity");
let TelemetryService = TelemetryService_1 = class TelemetryService {
telemetryRepository;
logger = new common_1.Logger(TelemetryService_1.name);
constructor(telemetryRepository) {
this.telemetryRepository = telemetryRepository;
}
async ingest(data) {
const log = this.telemetryRepository.create({
driverId: data.driver_id,
latitude: data.latitude,
longitude: data.longitude,
speed: data.speed,
heading: data.heading,
location: {
type: 'Point',
coordinates: [data.longitude, data.latitude],
},
});
await this.telemetryRepository.save(log);
this.logger.debug(`Ingested telemetry for driver ${data.driver_id}`);
return { success: true, timestamp: new Date() };
}
async getRecentDrivers(lat, lng, radiusMeters = 5000) {
return this.telemetryRepository
.createQueryBuilder('log')
.where('ST_DWithin(log.location, ST_MakePoint(:lng, :lat)::geography, :radius)')
.setParameters({ lat, lng, radius: radiusMeters })
.orderBy('log.timestamp', 'DESC')
.limit(50)
.getMany();
}
};
exports.TelemetryService = TelemetryService;
exports.TelemetryService = TelemetryService = TelemetryService_1 = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, typeorm_1.InjectRepository)(telemetry_entity_1.TelemetryLog)),
__metadata("design:paramtypes", [typeorm_2.Repository])
], TelemetryService);
//# sourceMappingURL=telemetry.service.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"telemetry.service.js","sourceRoot":"","sources":["../../src/telemetry/telemetry.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAoD;AACpD,6CAAmD;AACnD,qCAAqC;AACrC,yDAAkD;AAW3C,IAAM,gBAAgB,wBAAtB,MAAM,gBAAgB;IAKjB;IAJO,MAAM,GAAG,IAAI,eAAM,CAAC,kBAAgB,CAAC,IAAI,CAAC,CAAC;IAE5D,YAEU,mBAA6C;QAA7C,wBAAmB,GAAnB,mBAAmB,CAA0B;IACpD,CAAC;IAEJ,KAAK,CAAC,MAAM,CAAC,IAAwB;QAGnC,MAAM,GAAG,GAAG,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC;YAC1C,QAAQ,EAAE,IAAI,CAAC,SAAS;YACxB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAE;gBACR,IAAI,EAAE,OAAO;gBACb,WAAW,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC;aAC7C;SACF,CAAC,CAAC;QAEH,MAAM,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAIzC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QACrE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,GAAW,EAAE,GAAW,EAAE,eAAuB,IAAI;QAG1E,OAAO,IAAI,CAAC,mBAAmB;aAC5B,kBAAkB,CAAC,KAAK,CAAC;aACzB,KAAK,CAAC,wEAAwE,CAAC;aAC/E,aAAa,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;aACjD,OAAO,CAAC,eAAe,EAAE,MAAM,CAAC;aAChC,KAAK,CAAC,EAAE,CAAC;aACT,OAAO,EAAE,CAAC;IACf,CAAC;CACF,CAAA;AA1CY,4CAAgB;2BAAhB,gBAAgB;IAD5B,IAAA,mBAAU,GAAE;IAKR,WAAA,IAAA,0BAAgB,EAAC,+BAAY,CAAC,CAAA;qCACF,oBAAU;GAL9B,gBAAgB,CA0C5B"}
File diff suppressed because one or more lines are too long
+138 -3
View File
@@ -13,9 +13,11 @@
"@nestjs/config": "^4.0.3", "@nestjs/config": "^4.0.3",
"@nestjs/core": "^11.0.1", "@nestjs/core": "^11.0.1",
"@nestjs/platform-express": "^11.0.1", "@nestjs/platform-express": "^11.0.1",
"@nestjs/schedule": "^6.1.1",
"@nestjs/swagger": "^11.2.6", "@nestjs/swagger": "^11.2.6",
"@nestjs/typeorm": "^11.0.0", "@nestjs/typeorm": "^11.0.0",
"axios": "^1.13.6", "axios": "^1.13.6",
"mysql2": "^3.20.0",
"pg": "^8.20.0", "pg": "^8.20.0",
"redis": "^5.11.0", "redis": "^5.11.0",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
@@ -2258,6 +2260,18 @@
"@nestjs/core": "^11.0.0" "@nestjs/core": "^11.0.0"
} }
}, },
"node_modules/@nestjs/schedule": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-6.1.1.tgz",
"integrity": "sha512-kQl1RRgi02GJ0uaUGCrXHCcwISsCsJDciCKe38ykJZgnAeeoeVWs8luWtBo4AqAAXm4nS5K8RlV0smHUJ4+2FA==",
"dependencies": {
"cron": "4.4.0"
},
"peerDependencies": {
"@nestjs/common": "^10.0.0 || ^11.0.0",
"@nestjs/core": "^10.0.0 || ^11.0.0"
}
},
"node_modules/@nestjs/schematics": { "node_modules/@nestjs/schematics": {
"version": "11.0.9", "version": "11.0.9",
"resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.9.tgz", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.9.tgz",
@@ -2792,6 +2806,11 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true "dev": true
}, },
"node_modules/@types/luxon": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.1.tgz",
"integrity": "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg=="
},
"node_modules/@types/methods": { "node_modules/@types/methods": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
@@ -2802,7 +2821,6 @@
"version": "22.19.15", "version": "22.19.15",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz",
"integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==",
"devOptional": true,
"dependencies": { "dependencies": {
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
} }
@@ -3817,6 +3835,14 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/aws-ssl-profiles": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/axios": { "node_modules/axios": {
"version": "1.13.6", "version": "1.13.6",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
@@ -4572,6 +4598,22 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"devOptional": true "devOptional": true
}, },
"node_modules/cron": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/cron/-/cron-4.4.0.tgz",
"integrity": "sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==",
"dependencies": {
"@types/luxon": "~3.7.0",
"luxon": "~3.7.0"
},
"engines": {
"node": ">=18.x"
},
"funding": {
"type": "ko-fi",
"url": "https://ko-fi.com/intcreator"
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -4670,6 +4712,14 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"engines": {
"node": ">=0.10"
}
},
"node_modules/depd": { "node_modules/depd": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -5565,6 +5615,14 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/generate-function": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
"dependencies": {
"is-property": "^1.0.2"
}
},
"node_modules/gensync": { "node_modules/gensync": {
"version": "1.0.0-beta.2", "version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -6047,6 +6105,11 @@
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
}, },
"node_modules/is-property": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="
},
"node_modules/is-stream": { "node_modules/is-stream": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
@@ -7123,6 +7186,11 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -7132,6 +7200,28 @@
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"node_modules/lru.min": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
"integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==",
"engines": {
"bun": ">=1.0.0",
"deno": ">=1.30.0",
"node": ">=8.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wellwelwel"
}
},
"node_modules/luxon": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz",
"integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==",
"engines": {
"node": ">=12"
}
},
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.17", "version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
@@ -7394,6 +7484,38 @@
"node": "^18.17.0 || >=20.5.0" "node": "^18.17.0 || >=20.5.0"
} }
}, },
"node_modules/mysql2": {
"version": "3.20.0",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.20.0.tgz",
"integrity": "sha512-eCLUs7BNbgA6nf/MZXsaBO1SfGs0LtLVrJD3WeWq+jPLDWkSufTD+aGMwykfUVPdZnblaUK1a8G/P63cl9FkKg==",
"dependencies": {
"aws-ssl-profiles": "^1.1.2",
"denque": "^2.1.0",
"generate-function": "^2.3.1",
"iconv-lite": "^0.7.2",
"long": "^5.3.2",
"lru.min": "^1.1.4",
"named-placeholders": "^1.1.6",
"sql-escaper": "^1.3.3"
},
"engines": {
"node": ">= 8.0"
},
"peerDependencies": {
"@types/node": ">= 8"
}
},
"node_modules/named-placeholders": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
"integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
"dependencies": {
"lru.min": "^1.1.0"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/napi-postinstall": { "node_modules/napi-postinstall": {
"version": "0.3.4", "version": "0.3.4",
"resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
@@ -8512,6 +8634,20 @@
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"dev": true "dev": true
}, },
"node_modules/sql-escaper": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.3.3.tgz",
"integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==",
"engines": {
"bun": ">=1.0.0",
"deno": ">=2.0.0",
"node": ">=12.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/mysqljs/sql-escaper?sponsor=1"
}
},
"node_modules/sql-highlight": { "node_modules/sql-highlight": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/sql-highlight/-/sql-highlight-6.1.0.tgz", "resolved": "https://registry.npmjs.org/sql-highlight/-/sql-highlight-6.1.0.tgz",
@@ -9577,8 +9713,7 @@
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "6.21.0", "version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
"devOptional": true
}, },
"node_modules/universalify": { "node_modules/universalify": {
"version": "2.0.1", "version": "2.0.1",
+2
View File
@@ -24,9 +24,11 @@
"@nestjs/config": "^4.0.3", "@nestjs/config": "^4.0.3",
"@nestjs/core": "^11.0.1", "@nestjs/core": "^11.0.1",
"@nestjs/platform-express": "^11.0.1", "@nestjs/platform-express": "^11.0.1",
"@nestjs/schedule": "^6.1.1",
"@nestjs/swagger": "^11.2.6", "@nestjs/swagger": "^11.2.6",
"@nestjs/typeorm": "^11.0.0", "@nestjs/typeorm": "^11.0.0",
"axios": "^1.13.6", "axios": "^1.13.6",
"mysql2": "^3.20.0",
"pg": "^8.20.0", "pg": "^8.20.0",
"redis": "^5.11.0", "redis": "^5.11.0",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
+4
View File
@@ -1,14 +1,17 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config'; import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { ScheduleModule } from '@nestjs/schedule';
import { TelemetryModule } from './telemetry/telemetry.module'; import { TelemetryModule } from './telemetry/telemetry.module';
import { MapsModule } from './maps/maps.module'; import { MapsModule } from './maps/maps.module';
import { GeocodingModule } from './geocoding/geocoding.module';
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot({ ConfigModule.forRoot({
isGlobal: true, isGlobal: true,
}), }),
ScheduleModule.forRoot(),
TypeOrmModule.forRootAsync({ TypeOrmModule.forRootAsync({
imports: [ConfigModule], imports: [ConfigModule],
inject: [ConfigService], inject: [ConfigService],
@@ -22,6 +25,7 @@ import { MapsModule } from './maps/maps.module';
}), }),
TelemetryModule, TelemetryModule,
MapsModule, MapsModule,
GeocodingModule,
], ],
}) })
export class AppModule {} export class AppModule {}
@@ -0,0 +1,25 @@
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class ApiKeyGuard implements CanActivate {
constructor(private configService: ConfigService) {}
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const apiKeyHeader = request.headers['x-api-key'];
const validApiKey = this.configService.get<string>('API_KEY');
// If API_KEY is set in environment, enforce it
if (validApiKey && apiKeyHeader !== validApiKey) {
throw new UnauthorizedException('Invalid or missing API Key');
}
return true;
}
}
+28
View File
@@ -0,0 +1,28 @@
import { Module, Global } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { createClient } from 'redis';
import { RedisService } from './redis.service';
/**
* Global Redis Module - موديول Redis العالمي
*
* Provides a high-performance in-memory key-value store connection.
*/
@Global()
@Module({
providers: [
{
provide: 'REDIS_CLIENT',
useFactory: async (configService: ConfigService) => {
const url = configService.get<string>('REDIS_URL', 'redis://redis:6379');
const client = createClient({ url });
await client.connect();
return client;
},
inject: [ConfigService],
},
RedisService,
],
exports: ['REDIS_CLIENT', RedisService],
})
export class RedisModule {}
+54
View File
@@ -0,0 +1,54 @@
import { Injectable, Inject, OnModuleDestroy } from '@nestjs/common';
import type { RedisClientType } from 'redis';
/**
* Redis Service - خدمة التعامل مع الذاكرة العشوائية (Redis)
*
* Used for caching traffic snapshots and common routes.
*/
@Injectable()
export class RedisService implements OnModuleDestroy {
constructor(
@Inject('REDIS_CLIENT') private readonly redisClient: RedisClientType,
) {}
/**
* Set a value in Redis with optional TTL (Time To Live).
* حفظ قيمة في ذاكرة Redis مع وقت انتهاء اختياري
*/
async set(key: string, value: any, ttlSeconds?: number): Promise<void> {
const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
if (ttlSeconds) {
await this.redisClient.set(key, stringValue, { EX: ttlSeconds });
} else {
await this.redisClient.set(key, stringValue);
}
}
/**
* Get a value from Redis and parse it as JSON if possible.
* جلب قيمة من ذاكرة Redis وتحويلها إلى JSON إذا لزم الأمر
*/
async get<T>(key: string): Promise<T | null> {
const value = await this.redisClient.get(key);
if (!value) return null;
try {
return JSON.parse(value) as T;
} catch {
return value as unknown as T;
}
}
/**
* Delete a key from Redis.
* حذف مفتاح من ذاكرة Redis
*/
async del(key: string): Promise<void> {
await this.redisClient.del(key);
}
async onModuleDestroy() {
await this.redisClient.quit();
}
}
@@ -0,0 +1,31 @@
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity('osm_areas')
export class OsmArea {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'bigint', nullable: true })
osm_id: number;
@Column({ type: 'decimal', precision: 11, scale: 8, nullable: true })
longitude: number;
@Column({ type: 'decimal', precision: 10, scale: 8, nullable: true })
latitude: number;
@Column({ nullable: true })
name: string;
@Column({ nullable: true })
name_ar: string;
@Column({ nullable: true })
place_type: string;
@Column({ type: 'geometry', nullable: true })
geom: any;
@Column({ type: 'text', nullable: true })
other_tags: string;
}
@@ -0,0 +1,43 @@
import { Entity, Column, PrimaryColumn } from 'typeorm';
@Entity('osm_points_with_area')
export class OsmPointWithArea {
@PrimaryColumn({ type: 'bigint' })
osm_id: number;
@Column({ type: 'decimal', precision: 11, scale: 8, nullable: true })
longitude: number;
@Column({ type: 'decimal', precision: 10, scale: 8, nullable: true })
latitude: number;
@Column({ nullable: true })
name: string;
@Column({ nullable: true })
name_ar: string;
@Column({ nullable: true })
name_en: string;
@Column({ nullable: true })
amenity: string;
@Column({ nullable: true })
shop: string;
@Column({ nullable: true })
addr_street: string;
@Column({ nullable: true })
neighbourhood_name: string;
@Column({ nullable: true })
city_name: string;
@Column({ type: 'text', nullable: true })
other_tags: string;
@Column({ type: 'geometry', spatialFeatureType: 'Point', nullable: true })
geom: any;
}
@@ -0,0 +1,43 @@
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';
@Entity('places_syria')
export class PlaceSyria {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'decimal', precision: 10, scale: 8, nullable: true })
latitude: number;
@Column({ type: 'decimal', precision: 11, scale: 8, nullable: true })
longitude: number;
@Column({ nullable: true, unique: true })
name: string;
@Column({ nullable: true, unique: true })
name_ar: string;
@Column({ nullable: true })
name_en: string;
@Column({ nullable: true })
address: string;
@Column({ nullable: true })
category: string;
@Column({ nullable: true })
neighbourhood: string;
@Column({ nullable: true })
city: string;
@Column({ type: 'text', nullable: true })
description: string;
@CreateDateColumn()
created_at: Date;
@Column({ type: 'geometry', spatialFeatureType: 'Point', srid: 4326, nullable: true })
location: any;
}
@@ -0,0 +1,56 @@
import { Injectable, OnModuleInit, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { PlaceSyria } from './entities/place-syria.entity';
@Injectable()
export class GeocodingInitService implements OnModuleInit {
private readonly logger = new Logger(GeocodingInitService.name);
constructor(
@InjectRepository(PlaceSyria)
private readonly repo: Repository<PlaceSyria>,
) {}
async onModuleInit() {
this.logger.log('Checking for PostGIS extensions and triggers...');
try {
// Ensure PostGIS and Trigram extensions are enabled
await this.repo.query('CREATE EXTENSION IF NOT EXISTS postgis;');
await this.repo.query('CREATE EXTENSION IF NOT EXISTS pg_trgm;');
// Cleanup: Create a trigger to automatically update 'location' when lat/lng changes
await this.repo.query(`
CREATE OR REPLACE FUNCTION sync_place_location() RETURNS trigger AS $$
BEGIN
IF NEW.latitude IS NOT NULL AND NEW.longitude IS NOT NULL THEN
NEW.location := ST_SetSRID(ST_MakePoint(CAST(NEW.longitude AS FLOAT), CAST(NEW.latitude AS FLOAT)), 4326);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
`);
await this.repo.query(`
DROP TRIGGER IF EXISTS trg_sync_place_location ON places_syria;
CREATE TRIGGER trg_sync_place_location
BEFORE INSERT OR UPDATE ON places_syria
FOR EACH ROW EXECUTE FUNCTION sync_place_location();
`);
// 1. GIST Geometry Indexes (for fast proximity search)
await this.repo.query('CREATE INDEX IF NOT EXISTS idx_places_syria_location ON places_syria USING gist (location);');
await this.repo.query('CREATE INDEX IF NOT EXISTS idx_osm_areas_geom ON osm_areas USING gist (geom);');
await this.repo.query('CREATE INDEX IF NOT EXISTS idx_osm_points_geom ON osm_points_with_area USING gist (geom);');
// 2. GIST Trigram Indexes (for fast fuzzy name search and low server load)
await this.repo.query('CREATE INDEX IF NOT EXISTS idx_places_syria_names_trgm ON places_syria USING gist (name_ar gist_trgm_ops, name_en gist_trgm_ops);');
await this.repo.query('CREATE INDEX IF NOT EXISTS idx_osm_areas_names_trgm ON osm_areas USING gist (name_ar gist_trgm_ops);');
await this.repo.query('CREATE INDEX IF NOT EXISTS idx_osm_points_names_trgm ON osm_points_with_area USING gist (name_ar gist_trgm_ops, name_en gist_trgm_ops);');
this.logger.log('Geocoding database triggers and optimized indexes initialized.');
} catch (err) {
this.logger.error('Failed to initialize database geocoding triggers:', err);
}
}
}
@@ -0,0 +1,52 @@
import { Controller, Get, Post, Body, Query, UseGuards } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiQuery } from '@nestjs/swagger';
import { GeocodingService } from './geocoding.service';
import { ApiKeyGuard } from '../common/guards/api-key.guard';
@ApiTags('geocoding')
@Controller('geocoding')
@UseGuards(ApiKeyGuard)
export class GeocodingController {
constructor(private readonly geocodingService: GeocodingService) {}
@Get('search')
@ApiOperation({ summary: 'Search for locations (Forward Geocoding)' })
@ApiQuery({ name: 'q', required: true })
@ApiQuery({ name: 'lat', required: false, type: Number })
@ApiQuery({ name: 'lng', required: false, type: Number })
@ApiQuery({ name: 'radius', required: false, type: Number })
async search(
@Query('q') query: string,
@Query('lat') lat?: number,
@Query('lng') lng?: number,
@Query('radius') radius?: number,
) {
return this.geocodingService.searchPlaces(query, lat, lng, radius);
}
@Get('reverse')
@ApiOperation({ summary: 'Reverse Geocoding (Lat/Lng to Address)' })
@ApiQuery({ name: 'lat', required: true })
@ApiQuery({ name: 'lng', required: true })
async reverse(@Query('lat') lat: number, @Query('lng') lng: number) {
return this.geocodingService.reverseGeocode(lat, lng);
}
@Post('places')
@ApiOperation({ summary: 'Add a new location (User Submitted)' })
async addPlace(@Body() placeData: any) {
return this.geocodingService.addPlace(placeData);
}
@Get('places')
@ApiOperation({ summary: 'Get recent user submitted places' })
async getPlaces(@Query('limit') limit?: number) {
return this.geocodingService.getRecentPlaces(limit);
}
@Post('migrate')
@ApiOperation({ summary: 'Migrate legacy MySQL data to PostGIS' })
async migrate() {
return this.geocodingService.migrateFromMySQL('LEGACY_DB');
}
}
@@ -0,0 +1,21 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { GeocodingService } from './geocoding.service';
import { GeocodingInitService } from './geocoding-init.service';
import { GeocodingController } from './geocoding.controller';
import { PlaceSyria } from './entities/place-syria.entity';
import { OsmArea } from './entities/osm-area.entity';
import { OsmPointWithArea } from './entities/osm-point-with-area.entity';
@Module({
imports: [
TypeOrmModule.forFeature([
PlaceSyria,
OsmArea,
OsmPointWithArea
]),
],
controllers: [GeocodingController],
providers: [GeocodingService, GeocodingInitService],
})
export class GeocodingModule {}

Some files were not shown because too many files have changed in this diff Show More