2026-04-14-8 auth and commercial
This commit is contained in:
Vendored
+7
-15
@@ -14,6 +14,8 @@ 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");
|
||||
const auth_module_1 = require("./auth/auth.module");
|
||||
const throttler_1 = require("@nestjs/throttler");
|
||||
let AppModule = class AppModule {
|
||||
};
|
||||
exports.AppModule = AppModule;
|
||||
@@ -34,21 +36,11 @@ exports.AppModule = AppModule = __decorate([
|
||||
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,
|
||||
}),
|
||||
}),
|
||||
throttler_1.ThrottlerModule.forRoot([{
|
||||
ttl: 60,
|
||||
limit: 10,
|
||||
}]),
|
||||
auth_module_1.AuthModule,
|
||||
telemetry_module_1.TelemetryModule,
|
||||
maps_module_1.MapsModule,
|
||||
geocoding_module_1.GeocodingModule,
|
||||
|
||||
Vendored
+1
-1
@@ -1 +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"}
|
||||
{"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;AAC/D,oDAAgD;AAChD,iDAAoD;AA4B7C,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,8BAAS;oBAAT,SAAS;IA1BrB,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;iBAClB,CAAC;aACH,CAAC;YACF,2BAAe,CAAC,OAAO,CAAC,CAAC;oBACvB,GAAG,EAAE,EAAE;oBACP,KAAK,EAAE,EAAE;iBACV,CAAC,CAAC;YACH,wBAAU;YACV,kCAAe;YACf,wBAAU;YACV,kCAAe;SAChB;KACF,CAAC;GACW,SAAS,CAAG"}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
import { OnModuleInit } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
export declare class AuthModule implements OnModuleInit {
|
||||
private readonly authService;
|
||||
private readonly configService;
|
||||
constructor(authService: AuthService, configService: ConfigService);
|
||||
onModuleInit(): Promise<void>;
|
||||
}
|
||||
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
"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.AuthModule = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const typeorm_1 = require("@nestjs/typeorm");
|
||||
const auth_service_1 = require("./auth.service");
|
||||
const tenant_entity_1 = require("./entities/tenant.entity");
|
||||
const api_key_entity_1 = require("./entities/api-key.entity");
|
||||
const redis_module_1 = require("../common/redis.module");
|
||||
const config_1 = require("@nestjs/config");
|
||||
let AuthModule = class AuthModule {
|
||||
authService;
|
||||
configService;
|
||||
constructor(authService, configService) {
|
||||
this.authService = authService;
|
||||
this.configService = configService;
|
||||
}
|
||||
async onModuleInit() {
|
||||
const defaultKey = this.configService.get('MAP_API_KEY');
|
||||
if (defaultKey) {
|
||||
await this.authService.seedDefaultKey('Default System', 'admin@intaleq.xyz', defaultKey);
|
||||
console.log('✅ Default System API Key seeded successfully');
|
||||
}
|
||||
}
|
||||
};
|
||||
exports.AuthModule = AuthModule;
|
||||
exports.AuthModule = AuthModule = __decorate([
|
||||
(0, common_1.Global)(),
|
||||
(0, common_1.Module)({
|
||||
imports: [
|
||||
typeorm_1.TypeOrmModule.forFeature([tenant_entity_1.Tenant, api_key_entity_1.ApiKey]),
|
||||
redis_module_1.RedisModule,
|
||||
],
|
||||
providers: [auth_service_1.AuthService],
|
||||
exports: [auth_service_1.AuthService],
|
||||
}),
|
||||
__metadata("design:paramtypes", [auth_service_1.AuthService,
|
||||
config_1.ConfigService])
|
||||
], AuthModule);
|
||||
//# sourceMappingURL=auth.module.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.module.js","sourceRoot":"","sources":["../../src/auth/auth.module.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA8D;AAC9D,6CAAgD;AAChD,iDAA6C;AAC7C,4DAAkD;AAClD,8DAAmD;AACnD,yDAAqD;AACrD,2CAA+C;AAWxC,IAAM,UAAU,GAAhB,MAAM,UAAU;IAEF;IACA;IAFnB,YACmB,WAAwB,EACxB,aAA4B;QAD5B,gBAAW,GAAX,WAAW,CAAa;QACxB,kBAAa,GAAb,aAAa,CAAe;IAC5C,CAAC;IAMJ,KAAK,CAAC,YAAY;QAChB,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAS,aAAa,CAAC,CAAC;QACjE,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,gBAAgB,EAAE,mBAAmB,EAAE,UAAU,CAAC,CAAC;YACzF,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;CACF,CAAA;AAjBY,gCAAU;qBAAV,UAAU;IATtB,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,uBAAa,CAAC,UAAU,CAAC,CAAC,sBAAM,EAAE,uBAAM,CAAC,CAAC;YAC1C,0BAAW;SACZ;QACD,SAAS,EAAE,CAAC,0BAAW,CAAC;QACxB,OAAO,EAAE,CAAC,0BAAW,CAAC;KACvB,CAAC;qCAGgC,0BAAW;QACT,sBAAa;GAHpC,UAAU,CAiBtB"}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
import { Repository } from 'typeorm';
|
||||
import { ApiKey } from './entities/api-key.entity';
|
||||
import { Tenant } from './entities/tenant.entity';
|
||||
import { RedisService } from '../common/redis.service';
|
||||
export declare class AuthService {
|
||||
private readonly apiKeyRepository;
|
||||
private readonly tenantRepository;
|
||||
private readonly redisService;
|
||||
private readonly logger;
|
||||
constructor(apiKeyRepository: Repository<ApiKey>, tenantRepository: Repository<Tenant>, redisService: RedisService);
|
||||
validateApiKey(key: string, origin?: string, referer?: string): Promise<{
|
||||
tenant: Tenant;
|
||||
apiKey: ApiKey;
|
||||
rateLimit: number;
|
||||
}>;
|
||||
private validateRestrictions;
|
||||
private isDomainAllowed;
|
||||
hashSecret(secret: string): string;
|
||||
seedDefaultKey(name: string, email: string, keyString: string): Promise<void>;
|
||||
}
|
||||
Vendored
+115
@@ -0,0 +1,115 @@
|
||||
"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 AuthService_1;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AuthService = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const typeorm_1 = require("@nestjs/typeorm");
|
||||
const typeorm_2 = require("typeorm");
|
||||
const crypto_1 = require("crypto");
|
||||
const api_key_entity_1 = require("./entities/api-key.entity");
|
||||
const tenant_entity_1 = require("./entities/tenant.entity");
|
||||
const redis_service_1 = require("../common/redis.service");
|
||||
let AuthService = AuthService_1 = class AuthService {
|
||||
apiKeyRepository;
|
||||
tenantRepository;
|
||||
redisService;
|
||||
logger = new common_1.Logger(AuthService_1.name);
|
||||
constructor(apiKeyRepository, tenantRepository, redisService) {
|
||||
this.apiKeyRepository = apiKeyRepository;
|
||||
this.tenantRepository = tenantRepository;
|
||||
this.redisService = redisService;
|
||||
}
|
||||
async validateApiKey(key, origin, referer) {
|
||||
const cacheKey = `auth:apikey:${key}`;
|
||||
const cachedData = await this.redisService.get(cacheKey);
|
||||
if (cachedData) {
|
||||
this.validateRestrictions(cachedData.apiKey, origin, referer);
|
||||
return cachedData;
|
||||
}
|
||||
const apiKey = await this.apiKeyRepository.findOne({
|
||||
where: { key, isActive: true },
|
||||
relations: ['tenant'],
|
||||
});
|
||||
if (!apiKey || !apiKey.tenant || !apiKey.tenant.isActive) {
|
||||
throw new common_1.UnauthorizedException('Invalid or inactive API Key');
|
||||
}
|
||||
this.validateRestrictions(apiKey, origin, referer);
|
||||
const result = {
|
||||
tenant: apiKey.tenant,
|
||||
apiKey: apiKey,
|
||||
rateLimit: apiKey.rateLimit || 100,
|
||||
};
|
||||
await this.redisService.set(cacheKey, result, 3600);
|
||||
this.apiKeyRepository.update(apiKey.id, { lastUsedAt: new Date() }).catch(err => this.logger.error(`Failed to update lastUsedAt for API key ${apiKey.id}: ${err.message}`));
|
||||
return result;
|
||||
}
|
||||
validateRestrictions(apiKey, origin, referer) {
|
||||
if (apiKey.allowedOrigins && apiKey.allowedOrigins.length > 0) {
|
||||
if (!origin || !this.isDomainAllowed(origin, apiKey.allowedOrigins)) {
|
||||
this.logger.warn(`Origin mismatch for API key ${apiKey.id}. Received: ${origin}`);
|
||||
throw new common_1.UnauthorizedException('Request Origin not allowed for this API Key');
|
||||
}
|
||||
}
|
||||
if (apiKey.allowedReferrers && apiKey.allowedReferrers.length > 0) {
|
||||
if (!referer || !this.isDomainAllowed(referer, apiKey.allowedReferrers)) {
|
||||
this.logger.warn(`Referer mismatch for API key ${apiKey.id}. Received: ${referer}`);
|
||||
throw new common_1.UnauthorizedException('Request Referer not allowed for this API Key');
|
||||
}
|
||||
}
|
||||
}
|
||||
isDomainAllowed(domain, allowedList) {
|
||||
const cleanDomain = domain.replace(/^https?:\/\//, '').split('/')[0];
|
||||
return allowedList.some(allowed => {
|
||||
if (allowed === '*')
|
||||
return true;
|
||||
const regex = new RegExp('^' + allowed.replace(/\*/g, '.*') + '$');
|
||||
return regex.test(cleanDomain);
|
||||
});
|
||||
}
|
||||
hashSecret(secret) {
|
||||
return (0, crypto_1.createHash)('sha256').update(secret).digest('hex');
|
||||
}
|
||||
async seedDefaultKey(name, email, keyString) {
|
||||
let tenant = await this.tenantRepository.findOne({ where: { email } });
|
||||
if (!tenant) {
|
||||
tenant = await this.tenantRepository.save({
|
||||
name,
|
||||
email,
|
||||
isActive: true
|
||||
});
|
||||
}
|
||||
const existingKey = await this.apiKeyRepository.findOne({ where: { key: keyString } });
|
||||
if (!existingKey) {
|
||||
await this.apiKeyRepository.save({
|
||||
key: keyString,
|
||||
secretHash: this.hashSecret(keyString),
|
||||
name: 'Default Production Key',
|
||||
isActive: true,
|
||||
tenantId: tenant.id,
|
||||
rateLimit: 1000
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
exports.AuthService = AuthService;
|
||||
exports.AuthService = AuthService = AuthService_1 = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__param(0, (0, typeorm_1.InjectRepository)(api_key_entity_1.ApiKey)),
|
||||
__param(1, (0, typeorm_1.InjectRepository)(tenant_entity_1.Tenant)),
|
||||
__metadata("design:paramtypes", [typeorm_2.Repository,
|
||||
typeorm_2.Repository,
|
||||
redis_service_1.RedisService])
|
||||
], AuthService);
|
||||
//# sourceMappingURL=auth.service.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.service.js","sourceRoot":"","sources":["../../src/auth/auth.service.ts"],"names":[],"mappings":"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"}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { Tenant } from './tenant.entity';
|
||||
export declare class ApiKey {
|
||||
id: string;
|
||||
key: string;
|
||||
secretHash: string;
|
||||
name: string;
|
||||
isActive: boolean;
|
||||
rateLimit: number;
|
||||
allowedOrigins: string[];
|
||||
allowedReferrers: string[];
|
||||
tenant: Tenant;
|
||||
tenantId: string;
|
||||
lastUsedAt: Date;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
"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.ApiKey = void 0;
|
||||
const typeorm_1 = require("typeorm");
|
||||
const tenant_entity_1 = require("./tenant.entity");
|
||||
let ApiKey = class ApiKey {
|
||||
id;
|
||||
key;
|
||||
secretHash;
|
||||
name;
|
||||
isActive;
|
||||
rateLimit;
|
||||
allowedOrigins;
|
||||
allowedReferrers;
|
||||
tenant;
|
||||
tenantId;
|
||||
lastUsedAt;
|
||||
createdAt;
|
||||
updatedAt;
|
||||
};
|
||||
exports.ApiKey = ApiKey;
|
||||
__decorate([
|
||||
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
||||
__metadata("design:type", String)
|
||||
], ApiKey.prototype, "id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ unique: true }),
|
||||
(0, typeorm_1.Index)(),
|
||||
__metadata("design:type", String)
|
||||
], ApiKey.prototype, "key", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)(),
|
||||
__metadata("design:type", String)
|
||||
], ApiKey.prototype, "secretHash", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)(),
|
||||
__metadata("design:type", String)
|
||||
], ApiKey.prototype, "name", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ default: true }),
|
||||
__metadata("design:type", Boolean)
|
||||
], ApiKey.prototype, "isActive", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
__metadata("design:type", Number)
|
||||
], ApiKey.prototype, "rateLimit", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)('simple-array', { nullable: true }),
|
||||
__metadata("design:type", Array)
|
||||
], ApiKey.prototype, "allowedOrigins", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)('simple-array', { nullable: true }),
|
||||
__metadata("design:type", Array)
|
||||
], ApiKey.prototype, "allowedReferrers", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.ManyToOne)(() => tenant_entity_1.Tenant, (tenant) => tenant.apiKeys),
|
||||
__metadata("design:type", tenant_entity_1.Tenant)
|
||||
], ApiKey.prototype, "tenant", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)(),
|
||||
__metadata("design:type", String)
|
||||
], ApiKey.prototype, "tenantId", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
__metadata("design:type", Date)
|
||||
], ApiKey.prototype, "lastUsedAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.CreateDateColumn)(),
|
||||
__metadata("design:type", Date)
|
||||
], ApiKey.prototype, "createdAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.UpdateDateColumn)(),
|
||||
__metadata("design:type", Date)
|
||||
], ApiKey.prototype, "updatedAt", void 0);
|
||||
exports.ApiKey = ApiKey = __decorate([
|
||||
(0, typeorm_1.Entity)('api_keys')
|
||||
], ApiKey);
|
||||
//# sourceMappingURL=api-key.entity.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"api-key.entity.js","sourceRoot":"","sources":["../../../src/auth/entities/api-key.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAAuH;AACvH,mDAAyC;AAGlC,IAAM,MAAM,GAAZ,MAAM,MAAM;IAEjB,EAAE,CAAS;IAIX,GAAG,CAAS;IAGZ,UAAU,CAAS;IAGnB,IAAI,CAAS;IAGb,QAAQ,CAAU;IAGlB,SAAS,CAAS;IAGlB,cAAc,CAAW;IAGzB,gBAAgB,CAAW;IAG3B,MAAM,CAAS;IAGf,QAAQ,CAAS;IAGjB,UAAU,CAAO;IAGjB,SAAS,CAAO;IAGhB,SAAS,CAAO;CACjB,CAAA;AAxCY,wBAAM;AAEjB;IADC,IAAA,gCAAsB,EAAC,MAAM,CAAC;;kCACpB;AAIX;IAFC,IAAA,gBAAM,EAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACxB,IAAA,eAAK,GAAE;;mCACI;AAGZ;IADC,IAAA,gBAAM,GAAE;;0CACU;AAGnB;IADC,IAAA,gBAAM,GAAE;;oCACI;AAGb;IADC,IAAA,gBAAM,EAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;wCACR;AAGlB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;yCACT;AAGlB;IADC,IAAA,gBAAM,EAAC,cAAc,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;8CAClB;AAGzB;IADC,IAAA,gBAAM,EAAC,cAAc,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;gDAChB;AAG3B;IADC,IAAA,mBAAS,EAAC,GAAG,EAAE,CAAC,sBAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC;8BAC5C,sBAAM;sCAAC;AAGf;IADC,IAAA,gBAAM,GAAE;;wCACQ;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;8BACf,IAAI;0CAAC;AAGjB;IADC,IAAA,0BAAgB,GAAE;8BACR,IAAI;yCAAC;AAGhB;IADC,IAAA,0BAAgB,GAAE;8BACR,IAAI;yCAAC;iBAvCL,MAAM;IADlB,IAAA,gBAAM,EAAC,UAAU,CAAC;GACN,MAAM,CAwClB"}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { ApiKey } from './api-key.entity';
|
||||
export declare enum TenantPlan {
|
||||
FREE = "FREE",
|
||||
PREMIUM = "PREMIUM",
|
||||
ENTERPRISE = "ENTERPRISE"
|
||||
}
|
||||
export declare class Tenant {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
plan: TenantPlan;
|
||||
isActive: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
apiKeys: ApiKey[];
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
"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.Tenant = exports.TenantPlan = void 0;
|
||||
const typeorm_1 = require("typeorm");
|
||||
const api_key_entity_1 = require("./api-key.entity");
|
||||
var TenantPlan;
|
||||
(function (TenantPlan) {
|
||||
TenantPlan["FREE"] = "FREE";
|
||||
TenantPlan["PREMIUM"] = "PREMIUM";
|
||||
TenantPlan["ENTERPRISE"] = "ENTERPRISE";
|
||||
})(TenantPlan || (exports.TenantPlan = TenantPlan = {}));
|
||||
let Tenant = class Tenant {
|
||||
id;
|
||||
name;
|
||||
email;
|
||||
plan;
|
||||
isActive;
|
||||
createdAt;
|
||||
updatedAt;
|
||||
apiKeys;
|
||||
};
|
||||
exports.Tenant = Tenant;
|
||||
__decorate([
|
||||
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
||||
__metadata("design:type", String)
|
||||
], Tenant.prototype, "id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)(),
|
||||
__metadata("design:type", String)
|
||||
], Tenant.prototype, "name", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ unique: true }),
|
||||
__metadata("design:type", String)
|
||||
], Tenant.prototype, "email", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({
|
||||
type: 'enum',
|
||||
enum: TenantPlan,
|
||||
default: TenantPlan.FREE,
|
||||
}),
|
||||
__metadata("design:type", String)
|
||||
], Tenant.prototype, "plan", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ default: true }),
|
||||
__metadata("design:type", Boolean)
|
||||
], Tenant.prototype, "isActive", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.CreateDateColumn)(),
|
||||
__metadata("design:type", Date)
|
||||
], Tenant.prototype, "createdAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.UpdateDateColumn)(),
|
||||
__metadata("design:type", Date)
|
||||
], Tenant.prototype, "updatedAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.OneToMany)(() => api_key_entity_1.ApiKey, (apiKey) => apiKey.tenant),
|
||||
__metadata("design:type", Array)
|
||||
], Tenant.prototype, "apiKeys", void 0);
|
||||
exports.Tenant = Tenant = __decorate([
|
||||
(0, typeorm_1.Entity)('tenants')
|
||||
], Tenant);
|
||||
//# sourceMappingURL=tenant.entity.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"tenant.entity.js","sourceRoot":"","sources":["../../../src/auth/entities/tenant.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAAgH;AAChH,qDAA0C;AAE1C,IAAY,UAIX;AAJD,WAAY,UAAU;IACpB,2BAAa,CAAA;IACb,iCAAmB,CAAA;IACnB,uCAAyB,CAAA;AAC3B,CAAC,EAJW,UAAU,0BAAV,UAAU,QAIrB;AAGM,IAAM,MAAM,GAAZ,MAAM,MAAM;IAEjB,EAAE,CAAS;IAGX,IAAI,CAAS;IAGb,KAAK,CAAS;IAOd,IAAI,CAAa;IAGjB,QAAQ,CAAU;IAGlB,SAAS,CAAO;IAGhB,SAAS,CAAO;IAGhB,OAAO,CAAW;CACnB,CAAA;AA5BY,wBAAM;AAEjB;IADC,IAAA,gCAAsB,EAAC,MAAM,CAAC;;kCACpB;AAGX;IADC,IAAA,gBAAM,GAAE;;oCACI;AAGb;IADC,IAAA,gBAAM,EAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;;qCACX;AAOd;IALC,IAAA,gBAAM,EAAC;QACN,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,UAAU,CAAC,IAAI;KACzB,CAAC;;oCACe;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;wCACR;AAGlB;IADC,IAAA,0BAAgB,GAAE;8BACR,IAAI;yCAAC;AAGhB;IADC,IAAA,0BAAgB,GAAE;8BACR,IAAI;yCAAC;AAGhB;IADC,IAAA,mBAAS,EAAC,GAAG,EAAE,CAAC,uBAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC;;uCACjC;iBA3BP,MAAM;IADlB,IAAA,gBAAM,EAAC,SAAS,CAAC;GACL,MAAM,CA4BlB"}
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
import { CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { AuthService } from '../../auth/auth.service';
|
||||
export declare class ApiKeyGuard implements CanActivate {
|
||||
private configService;
|
||||
constructor(configService: ConfigService);
|
||||
canActivate(context: ExecutionContext): boolean;
|
||||
private authService;
|
||||
constructor(authService: AuthService);
|
||||
canActivate(context: ExecutionContext): Promise<boolean>;
|
||||
}
|
||||
|
||||
+21
-11
@@ -11,25 +11,35 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ApiKeyGuard = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const config_1 = require("@nestjs/config");
|
||||
const auth_service_1 = require("../../auth/auth.service");
|
||||
let ApiKeyGuard = class ApiKeyGuard {
|
||||
configService;
|
||||
constructor(configService) {
|
||||
this.configService = configService;
|
||||
authService;
|
||||
constructor(authService) {
|
||||
this.authService = authService;
|
||||
}
|
||||
canActivate(context) {
|
||||
async 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');
|
||||
const apiKeyHeader = request.headers['x-api-key'] || request.query['api_key'];
|
||||
if (!apiKeyHeader) {
|
||||
throw new common_1.UnauthorizedException('API Key (x-api-key) is missing');
|
||||
}
|
||||
const origin = request.headers['origin'];
|
||||
const referer = request.headers['referer'];
|
||||
try {
|
||||
const { tenant, apiKey, rateLimit } = await this.authService.validateApiKey(apiKeyHeader, origin, referer);
|
||||
request['tenant'] = tenant;
|
||||
request['apiKey'] = apiKey;
|
||||
request['rateLimit'] = rateLimit;
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
throw new common_1.UnauthorizedException(error.message || 'Invalid API Key');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
exports.ApiKeyGuard = ApiKeyGuard;
|
||||
exports.ApiKeyGuard = ApiKeyGuard = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__metadata("design:paramtypes", [config_1.ConfigService])
|
||||
__metadata("design:paramtypes", [auth_service_1.AuthService])
|
||||
], ApiKeyGuard);
|
||||
//# sourceMappingURL=api-key.guard.js.map
|
||||
+1
-1
@@ -1 +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"}
|
||||
{"version":3,"file":"api-key.guard.js","sourceRoot":"","sources":["../../../src/common/guards/api-key.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAKwB;AACxB,0DAAsD;AAG/C,IAAM,WAAW,GAAjB,MAAM,WAAW;IACF;IAApB,YAAoB,WAAwB;QAAxB,gBAAW,GAAX,WAAW,CAAa;IAAG,CAAC;IAEhD,KAAK,CAAC,WAAW,CAAC,OAAyB;QACzC,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QAGpD,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAE9E,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,MAAM,IAAI,8BAAqB,CAAC,gCAAgC,CAAC,CAAC;QACpE,CAAC;QAGD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACzC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAE3C,IAAI,CAAC;YAEH,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,cAAc,CACzE,YAAY,EACZ,MAAM,EACN,OAAO,CACR,CAAC;YAGF,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;YAC3B,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;YAC3B,OAAO,CAAC,WAAW,CAAC,GAAG,SAAS,CAAC;YAEjC,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,8BAAqB,CAAC,KAAK,CAAC,OAAO,IAAI,iBAAiB,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;CACF,CAAA;AAnCY,kCAAW;sBAAX,WAAW;IADvB,IAAA,mBAAU,GAAE;qCAEsB,0BAAW;GADjC,WAAW,CAmCvB"}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { ThrottlerGuard, ThrottlerRequest } from '@nestjs/throttler';
|
||||
export declare class TenantThrottlerGuard extends ThrottlerGuard {
|
||||
protected getTracker(req: Record<string, any>): Promise<string>;
|
||||
protected handleRequest(requestProps: ThrottlerRequest): Promise<boolean>;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
"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.TenantThrottlerGuard = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const throttler_1 = require("@nestjs/throttler");
|
||||
let TenantThrottlerGuard = class TenantThrottlerGuard extends throttler_1.ThrottlerGuard {
|
||||
async getTracker(req) {
|
||||
const apiKeyId = req['apiKey']?.id || req.ip;
|
||||
return `throttler:key:${apiKeyId}`;
|
||||
}
|
||||
async handleRequest(requestProps) {
|
||||
const { context, limit } = requestProps;
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const dynamicLimit = request['rateLimit'] || limit;
|
||||
requestProps.limit = dynamicLimit;
|
||||
return super.handleRequest(requestProps);
|
||||
}
|
||||
};
|
||||
exports.TenantThrottlerGuard = TenantThrottlerGuard;
|
||||
exports.TenantThrottlerGuard = TenantThrottlerGuard = __decorate([
|
||||
(0, common_1.Injectable)()
|
||||
], TenantThrottlerGuard);
|
||||
//# sourceMappingURL=rate-limiter.guard.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"rate-limiter.guard.js","sourceRoot":"","sources":["../../../src/common/guards/rate-limiter.guard.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA8D;AAC9D,iDAAqE;AAG9D,IAAM,oBAAoB,GAA1B,MAAM,oBAAqB,SAAQ,0BAAc;IAK5C,KAAK,CAAC,UAAU,CAAC,GAAwB;QAEjD,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,GAAG,CAAC,EAAE,CAAC;QAC7C,OAAO,iBAAiB,QAAQ,EAAE,CAAC;IACrC,CAAC;IAKS,KAAK,CAAC,aAAa,CAC3B,YAA8B;QAE9B,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,YAAY,CAAC;QACxC,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QAGpD,MAAM,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC;QAGnD,YAAY,CAAC,KAAK,GAAG,YAAY,CAAC;QAGlC,OAAO,KAAK,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;IAC3C,CAAC;CACF,CAAA;AA7BY,oDAAoB;+BAApB,oBAAoB;IADhC,IAAA,mBAAU,GAAE;GACA,oBAAoB,CA6BhC"}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
export declare class TelegramService {
|
||||
private configService;
|
||||
private readonly logger;
|
||||
private readonly botToken;
|
||||
private readonly chatId;
|
||||
constructor(configService: ConfigService);
|
||||
sendMessage(text: string): Promise<boolean>;
|
||||
sendIntelligenceReport(stats: {
|
||||
syncResult: number;
|
||||
updatedSegments: number;
|
||||
discoveredRoads: number;
|
||||
totalPoints: number;
|
||||
days: number;
|
||||
timeProfiles?: number;
|
||||
}): Promise<boolean>;
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
"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 TelegramService_1;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.TelegramService = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const config_1 = require("@nestjs/config");
|
||||
const axios_1 = __importDefault(require("axios"));
|
||||
let TelegramService = TelegramService_1 = class TelegramService {
|
||||
configService;
|
||||
logger = new common_1.Logger(TelegramService_1.name);
|
||||
botToken;
|
||||
chatId;
|
||||
constructor(configService) {
|
||||
this.configService = configService;
|
||||
this.botToken = this.configService.get('TELEGRAM_BOT_TOKEN') || '';
|
||||
this.chatId = this.configService.get('TELEGRAM_CHAT_ID') || '';
|
||||
}
|
||||
async sendMessage(text) {
|
||||
if (!this.botToken || !this.chatId || this.botToken.includes('YOUR_BOT_TOKEN')) {
|
||||
this.logger.warn('Telegram Bot Token or Chat ID not configured. Skipping notification.');
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
this.logger.log('Sending report to Telegram...');
|
||||
const url = `https://api.telegram.org/bot${this.botToken}/sendMessage`;
|
||||
await axios_1.default.post(url, {
|
||||
chat_id: this.chatId,
|
||||
text: text,
|
||||
parse_mode: 'HTML',
|
||||
});
|
||||
this.logger.log('✅ Telegram notification sent successfully.');
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error(`❌ Failed to send Telegram message: ${error.message}`);
|
||||
if (error.response) {
|
||||
this.logger.error(`Status: ${error.response.status}, Data: ${JSON.stringify(error.response.data)}`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
async sendIntelligenceReport(stats) {
|
||||
const message = `
|
||||
🚀 <b>Intaleq Map Intelligence Report</b>
|
||||
━━━━━━━━━━━━━━━━━━
|
||||
📅 <b>Window:</b> Last ${stats.days} days
|
||||
📥 <b>Tracks Imported:</b> ${stats.syncResult} points
|
||||
🛣️ <b>Roads Updated:</b> ${stats.updatedSegments} segments
|
||||
✨ <b>New Roads Found:</b> ${stats.discoveredRoads} candidates
|
||||
🕒 <b>Time Profiles:</b> ${stats.timeProfiles || 0} buckets
|
||||
📍 <b>Database Total:</b> ${stats.totalPoints} points
|
||||
━━━━━━━━━━━━━━━━━━
|
||||
✅ <i>Deep Sync and Analysis Complete.</i>
|
||||
`;
|
||||
return this.sendMessage(message);
|
||||
}
|
||||
};
|
||||
exports.TelegramService = TelegramService;
|
||||
exports.TelegramService = TelegramService = TelegramService_1 = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__metadata("design:paramtypes", [config_1.ConfigService])
|
||||
], TelegramService);
|
||||
//# sourceMappingURL=telegram.service.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"telegram.service.js","sourceRoot":"","sources":["../../src/common/telegram.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAoD;AACpD,2CAA+C;AAC/C,kDAA0B;AAGnB,IAAM,eAAe,uBAArB,MAAM,eAAe;IAKN;IAJH,MAAM,GAAG,IAAI,eAAM,CAAC,iBAAe,CAAC,IAAI,CAAC,CAAC;IAC1C,QAAQ,CAAS;IACjB,MAAM,CAAS;IAEhC,YAAoB,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;QAC9C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAS,oBAAoB,CAAC,IAAI,EAAE,CAAC;QAC3E,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAS,kBAAkB,CAAC,IAAI,EAAE,CAAC;IACzE,CAAC;IAMD,KAAK,CAAC,WAAW,CAAC,IAAY;QAC5B,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;YAC/E,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,sEAAsE,CAAC,CAAC;YACzF,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;YACjD,MAAM,GAAG,GAAG,+BAA+B,IAAI,CAAC,QAAQ,cAAc,CAAC;YACvE,MAAM,eAAK,CAAC,IAAI,CAAC,GAAG,EAAE;gBACpB,OAAO,EAAE,IAAI,CAAC,MAAM;gBACpB,IAAI,EAAE,IAAI;gBACV,UAAU,EAAE,MAAM;aACnB,CAAC,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;YAC9D,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,sCAAsC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACzE,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACnB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,KAAK,CAAC,QAAQ,CAAC,MAAM,WAAW,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACtG,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAKD,KAAK,CAAC,sBAAsB,CAAC,KAO5B;QACC,MAAM,OAAO,GAAG;;;yBAGK,KAAK,CAAC,IAAI;6BACN,KAAK,CAAC,UAAU;4BACjB,KAAK,CAAC,eAAe;4BACrB,KAAK,CAAC,eAAe;2BACtB,KAAK,CAAC,YAAY,IAAI,CAAC;4BACtB,KAAK,CAAC,WAAW;;;KAGxC,CAAC;QACF,OAAO,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;CACF,CAAA;AAhEY,0CAAe;0BAAf,eAAe;IAD3B,IAAA,mBAAU,GAAE;qCAMwB,sBAAa;GALrC,eAAe,CAgE3B"}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Repository } from 'typeorm';
|
||||
import { AdminBoundary } from './entities/admin-boundary.entity';
|
||||
export declare class AdminBoundariesService {
|
||||
private adminBoundaryRepo;
|
||||
private readonly logger;
|
||||
constructor(adminBoundaryRepo: Repository<AdminBoundary>);
|
||||
importGeoJSON(targetCountryCode: string, geoJson: any): Promise<{
|
||||
success: boolean;
|
||||
imported: number;
|
||||
errors: number;
|
||||
skipped: number;
|
||||
}>;
|
||||
importFromFile(countryCode: string, filePath: string): Promise<{
|
||||
success: boolean;
|
||||
imported: number;
|
||||
errors: number;
|
||||
skipped: number;
|
||||
}>;
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
"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 AdminBoundariesService_1;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AdminBoundariesService = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const typeorm_1 = require("@nestjs/typeorm");
|
||||
const typeorm_2 = require("typeorm");
|
||||
const admin_boundary_entity_1 = require("./entities/admin-boundary.entity");
|
||||
let AdminBoundariesService = AdminBoundariesService_1 = class AdminBoundariesService {
|
||||
adminBoundaryRepo;
|
||||
logger = new common_1.Logger(AdminBoundariesService_1.name);
|
||||
constructor(adminBoundaryRepo) {
|
||||
this.adminBoundaryRepo = adminBoundaryRepo;
|
||||
}
|
||||
async importGeoJSON(targetCountryCode, geoJson) {
|
||||
this.logger.log(`Importing admin boundaries for ${targetCountryCode}...`);
|
||||
let imported = 0;
|
||||
let errors = 0;
|
||||
let skipped = 0;
|
||||
if (!geoJson || !geoJson.features || !Array.isArray(geoJson.features)) {
|
||||
this.logger.error('Invalid GeoJSON format');
|
||||
return { success: false, imported: 0, errors: 1, skipped: 0 };
|
||||
}
|
||||
for (const feature of geoJson.features) {
|
||||
try {
|
||||
const props = feature.properties || {};
|
||||
const countryCode = props.country || props.iso_3166_1_alpha2;
|
||||
if (targetCountryCode && countryCode && countryCode.toUpperCase() !== targetCountryCode.toUpperCase()) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
const subtype = (props.subtype || '').toLowerCase();
|
||||
let adminLevel;
|
||||
switch (subtype) {
|
||||
case 'country':
|
||||
adminLevel = 2;
|
||||
break;
|
||||
case 'region':
|
||||
adminLevel = 4;
|
||||
break;
|
||||
case 'county':
|
||||
adminLevel = 6;
|
||||
break;
|
||||
case 'localadmin':
|
||||
adminLevel = 8;
|
||||
break;
|
||||
case 'locality':
|
||||
adminLevel = 8;
|
||||
break;
|
||||
case 'neighborhood':
|
||||
adminLevel = 10;
|
||||
break;
|
||||
case 'macrohood':
|
||||
adminLevel = 9;
|
||||
break;
|
||||
case 'microhood':
|
||||
adminLevel = 11;
|
||||
break;
|
||||
default:
|
||||
adminLevel = parseInt(props.admin_level || props.adminLevel, 10);
|
||||
}
|
||||
if (isNaN(adminLevel)) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
const nameAr = props['names']?.['primary'] || props['names']?.['common'] || props['name_ar'] || props['name'];
|
||||
const nameEn = props['names']?.['en'] || props['name_en'];
|
||||
const isArabic = (text) => /[\u0600-\u06FF]/.test(text);
|
||||
if (['JO', 'SY', 'EG'].includes(targetCountryCode.toUpperCase()) && nameAr && !isArabic(nameAr)) {
|
||||
const alternativeAr = Object.values(props['names'] || {}).find(v => typeof v === 'string' && isArabic(v));
|
||||
if (!alternativeAr) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let geom = feature.geometry;
|
||||
if (geom.type === 'Polygon') {
|
||||
geom = { type: 'MultiPolygon', coordinates: [geom.coordinates] };
|
||||
}
|
||||
else if (geom.type !== 'MultiPolygon') {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
const boundary = this.adminBoundaryRepo.create({
|
||||
country_code: targetCountryCode.toUpperCase(),
|
||||
admin_level: adminLevel,
|
||||
name_ar: nameAr,
|
||||
name_en: nameEn,
|
||||
geom: geom,
|
||||
});
|
||||
await this.adminBoundaryRepo.save(boundary);
|
||||
imported++;
|
||||
}
|
||||
catch (err) {
|
||||
this.logger.error(`Error importing feature: ${err.message}`);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
this.logger.log(`Import finished. Imported: ${imported}, Errors: ${errors}, Skipped: ${skipped}`);
|
||||
return { success: true, imported, errors, skipped };
|
||||
}
|
||||
async importFromFile(countryCode, filePath) {
|
||||
const fs = require('fs');
|
||||
if (!fs.existsSync(filePath)) {
|
||||
this.logger.error(`File not found: ${filePath}`);
|
||||
return { success: false, imported: 0, errors: 1, skipped: 0 };
|
||||
}
|
||||
try {
|
||||
const data = fs.readFileSync(filePath, 'utf8');
|
||||
const geoJson = JSON.parse(data);
|
||||
return this.importGeoJSON(countryCode, geoJson);
|
||||
}
|
||||
catch (err) {
|
||||
this.logger.error(`Failed to read or parse GeoJSON file: ${err.message}`);
|
||||
return { success: false, imported: 0, errors: 1, skipped: 0 };
|
||||
}
|
||||
}
|
||||
};
|
||||
exports.AdminBoundariesService = AdminBoundariesService;
|
||||
exports.AdminBoundariesService = AdminBoundariesService = AdminBoundariesService_1 = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__param(0, (0, typeorm_1.InjectRepository)(admin_boundary_entity_1.AdminBoundary)),
|
||||
__metadata("design:paramtypes", [typeorm_2.Repository])
|
||||
], AdminBoundariesService);
|
||||
//# sourceMappingURL=admin-boundaries.service.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"admin-boundaries.service.js","sourceRoot":"","sources":["../../src/geocoding/admin-boundaries.service.ts"],"names":[],"mappings":"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"}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Repository, DataSource } from 'typeorm';
|
||||
import { NeighborhoodPoint } from './entities/neighborhood-point.entity';
|
||||
import { NeighborhoodPolygon } from './entities/neighborhood-polygon.entity';
|
||||
import { AdminBoundary } from './entities/admin-boundary.entity';
|
||||
export declare class AdministrativeLinkingService {
|
||||
private dataSource;
|
||||
private readonly neighborhoodPointRepo;
|
||||
private readonly neighborhoodPolygonRepo;
|
||||
private readonly adminBoundaryRepo;
|
||||
private readonly logger;
|
||||
constructor(dataSource: DataSource, neighborhoodPointRepo: Repository<NeighborhoodPoint>, neighborhoodPolygonRepo: Repository<NeighborhoodPolygon>, adminBoundaryRepo: Repository<AdminBoundary>);
|
||||
private readonly overpassMirrors;
|
||||
private fetchWithRetry;
|
||||
syncOsmNeighborhoodPoints(bbox?: string, country?: string): Promise<{
|
||||
detected_country: string;
|
||||
osm_fetched: any;
|
||||
country_points: number;
|
||||
total_points_in_db: number;
|
||||
linked_to_district: number;
|
||||
}>;
|
||||
generateVoronoiNeighborhoods(country?: string): Promise<{
|
||||
status: string;
|
||||
country: string;
|
||||
total_polygons: number;
|
||||
districts_processed: any;
|
||||
}>;
|
||||
linkPlaces(country: 'jordan' | 'syria' | 'egypt'): Promise<{
|
||||
status: string;
|
||||
country: "syria" | "jordan" | "egypt";
|
||||
total_places: number;
|
||||
linked_neighborhood: number;
|
||||
linked_district: number;
|
||||
linked_governorate: number;
|
||||
sample_masoum: any;
|
||||
rows_affected: any;
|
||||
} | {
|
||||
status: string;
|
||||
error: string;
|
||||
polygons: number;
|
||||
places: number;
|
||||
}>;
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
"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 };
|
||||
};
|
||||
var AdministrativeLinkingService_1;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AdministrativeLinkingService = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const typeorm_1 = require("@nestjs/typeorm");
|
||||
const typeorm_2 = require("typeorm");
|
||||
const neighborhood_point_entity_1 = require("./entities/neighborhood-point.entity");
|
||||
const neighborhood_polygon_entity_1 = require("./entities/neighborhood-polygon.entity");
|
||||
const admin_boundary_entity_1 = require("./entities/admin-boundary.entity");
|
||||
const axios_1 = __importDefault(require("axios"));
|
||||
let AdministrativeLinkingService = AdministrativeLinkingService_1 = class AdministrativeLinkingService {
|
||||
dataSource;
|
||||
neighborhoodPointRepo;
|
||||
neighborhoodPolygonRepo;
|
||||
adminBoundaryRepo;
|
||||
logger = new common_1.Logger(AdministrativeLinkingService_1.name);
|
||||
constructor(dataSource, neighborhoodPointRepo, neighborhoodPolygonRepo, adminBoundaryRepo) {
|
||||
this.dataSource = dataSource;
|
||||
this.neighborhoodPointRepo = neighborhoodPointRepo;
|
||||
this.neighborhoodPolygonRepo = neighborhoodPolygonRepo;
|
||||
this.adminBoundaryRepo = adminBoundaryRepo;
|
||||
}
|
||||
overpassMirrors = [
|
||||
'https://overpass-api.de/api/interpreter',
|
||||
'https://overpass.kumi.systems/api/interpreter',
|
||||
'https://z.overpass-api.de/api/interpreter'
|
||||
];
|
||||
async fetchWithRetry(data, retries = 3) {
|
||||
for (let i = 0; i <= retries; i++) {
|
||||
const url = this.overpassMirrors[i % this.overpassMirrors.length];
|
||||
try {
|
||||
const response = await axios_1.default.post(url, data, {
|
||||
timeout: 45000,
|
||||
headers: {
|
||||
'User-Agent': 'IntaleqMapBot/1.0',
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.warn(`Mirror ${url} failed: ${error.message}. Trying next mirror...`);
|
||||
if (i === retries)
|
||||
throw error;
|
||||
await new Promise(res => setTimeout(res, 2000));
|
||||
}
|
||||
}
|
||||
}
|
||||
async syncOsmNeighborhoodPoints(bbox = '31.86,35.94,32.22,36.25', country) {
|
||||
this.logger.log(`Syncing neighborhood points for country: ${country || 'auto'}, bbox: ${bbox}`);
|
||||
const query = `[out:json][timeout:900];node["place"~"neighbourhood|suburb|town"](${bbox});out;`;
|
||||
try {
|
||||
const data = await this.fetchWithRetry(`data=${encodeURIComponent(query)}`);
|
||||
const elements = data.elements;
|
||||
this.logger.log(`Found ${elements.length} points in OSM.`);
|
||||
let detectedCountry = country;
|
||||
if (!detectedCountry) {
|
||||
const firstLat = parseFloat(bbox.split(',')[0]);
|
||||
if (firstLat > 32.3)
|
||||
detectedCountry = 'syria';
|
||||
else if (firstLat < 31.0)
|
||||
detectedCountry = 'egypt';
|
||||
else
|
||||
detectedCountry = 'jordan';
|
||||
}
|
||||
for (const e of elements) {
|
||||
const nameAr = e.tags['name:ar'] || e.tags['name'];
|
||||
const nameEn = e.tags['name:en'] || e.tags['name'];
|
||||
const geometry = { type: 'Point', coordinates: [e.lon, e.lat] };
|
||||
await this.dataSource.query(`
|
||||
INSERT INTO neighborhood_points (osm_id, name_ar, name_en, place_type, geometry, country)
|
||||
VALUES ($1, $2, $3, $4, ST_SetSRID(ST_GeomFromGeoJSON($5), 4326), $6)
|
||||
ON CONFLICT (osm_id) DO UPDATE SET
|
||||
name_ar = EXCLUDED.name_ar,
|
||||
name_en = EXCLUDED.name_en,
|
||||
place_type = EXCLUDED.place_type,
|
||||
geometry = EXCLUDED.geometry,
|
||||
country = COALESCE(EXCLUDED.country, neighborhood_points.country);
|
||||
`, [e.id, nameAr, nameEn, e.tags['place'], JSON.stringify(geometry), detectedCountry]);
|
||||
}
|
||||
await this.dataSource.query(`
|
||||
UPDATE neighborhood_points np
|
||||
SET district_id = COALESCE(
|
||||
(
|
||||
SELECT id FROM admin_boundaries ab
|
||||
WHERE ab.admin_level IN (6, 8)
|
||||
AND ST_Contains(ab.geom::geometry, np.geometry::geometry)
|
||||
ORDER BY ab.admin_level DESC
|
||||
LIMIT 1
|
||||
),
|
||||
(
|
||||
SELECT id FROM admin_boundaries ab
|
||||
WHERE ab.admin_level IN (6, 8)
|
||||
ORDER BY ab.geom::geometry <-> np.geometry::geometry
|
||||
LIMIT 1
|
||||
)
|
||||
)
|
||||
WHERE district_id IS NULL AND (country = $1 OR $1 IS NULL);
|
||||
`, [detectedCountry]);
|
||||
const totalPoints = await this.dataSource.query(`SELECT count(*) as cnt FROM neighborhood_points`);
|
||||
const linkedPoints = await this.dataSource.query(`SELECT count(*) as cnt FROM neighborhood_points WHERE district_id IS NOT NULL`);
|
||||
const countryPoints = await this.dataSource.query(`SELECT count(*) as cnt FROM neighborhood_points WHERE country = $1`, [detectedCountry]);
|
||||
const diagnostics = {
|
||||
detected_country: detectedCountry,
|
||||
osm_fetched: elements.length,
|
||||
country_points: parseInt(countryPoints[0].cnt),
|
||||
total_points_in_db: parseInt(totalPoints[0].cnt),
|
||||
linked_to_district: parseInt(linkedPoints[0].cnt),
|
||||
};
|
||||
this.logger.log(`Step 1 diagnostics: ${JSON.stringify(diagnostics)}`);
|
||||
return diagnostics;
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error(`Failed to sync OSM points: ${error.message}`, error.stack);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async generateVoronoiNeighborhoods(country) {
|
||||
this.logger.log(`Generating Voronoi polygons for ${country || 'all'} neighborhoods...`);
|
||||
const whereClause = country ? `WHERE district_id IS NOT NULL AND country = '${country}'` : `WHERE district_id IS NOT NULL`;
|
||||
const prePoints = await this.dataSource.query(`SELECT count(*) as cnt FROM neighborhood_points ${whereClause}`);
|
||||
const preDistricts = await this.dataSource.query(`SELECT district_id, count(*) as cnt FROM neighborhood_points ${whereClause} GROUP BY district_id`);
|
||||
const deleteWhere = country ? `WHERE method = 'voronoi' AND country = '${country}'` : `WHERE method = 'voronoi'`;
|
||||
await this.dataSource.query(`DELETE FROM neighborhood_polygons ${deleteWhere}`);
|
||||
this.logger.log(`Deleted old voronoi polygons for ${country || 'all'}.`);
|
||||
let totalInserted = 0;
|
||||
for (const row of preDistricts) {
|
||||
const districtId = row.district_id;
|
||||
const pointCount = parseInt(row.cnt);
|
||||
try {
|
||||
if (pointCount >= 2) {
|
||||
const result = await this.dataSource.query(`
|
||||
INSERT INTO neighborhood_polygons (osm_id, name_ar, name_en, place_type, parent_id, method, country, geometry)
|
||||
WITH voronoi_cells AS (
|
||||
SELECT (ST_Dump(ST_VoronoiPolygons(
|
||||
ST_Collect(np.geometry::geometry),
|
||||
0.00001,
|
||||
d.geom::geometry
|
||||
))).geom as cell
|
||||
FROM neighborhood_points np
|
||||
JOIN admin_boundaries d ON d.id = np.district_id
|
||||
WHERE np.district_id = $1
|
||||
GROUP BY d.geom
|
||||
),
|
||||
matched AS (
|
||||
SELECT
|
||||
np.osm_id, np.name_ar, np.name_en, np.place_type, np.district_id, np.country,
|
||||
ST_Multi(ST_Intersection(vc.cell::geometry, d.geom::geometry)) as geometry
|
||||
FROM voronoi_cells vc
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT np2.*
|
||||
FROM neighborhood_points np2
|
||||
WHERE np2.district_id = $1
|
||||
ORDER BY np2.geometry::geometry <-> vc.cell::geometry
|
||||
LIMIT 1
|
||||
) np
|
||||
JOIN admin_boundaries d ON d.id = $1
|
||||
)
|
||||
SELECT osm_id, name_ar, name_en, place_type, district_id, 'voronoi', country, geometry
|
||||
FROM matched
|
||||
WHERE ST_IsValid(geometry) AND NOT ST_IsEmpty(geometry)
|
||||
`, [districtId]);
|
||||
totalInserted += (result?.length || result?.[1] || 0);
|
||||
}
|
||||
else if (pointCount === 1) {
|
||||
await this.dataSource.query(`
|
||||
INSERT INTO neighborhood_polygons (osm_id, name_ar, name_en, place_type, parent_id, method, country, geometry)
|
||||
SELECT np.osm_id, np.name_ar, np.name_en, np.place_type, np.district_id, 'voronoi', np.country, ST_Multi(d.geom::geometry)
|
||||
FROM neighborhood_points np
|
||||
JOIN admin_boundaries d ON d.id = np.district_id
|
||||
WHERE np.district_id = $1
|
||||
`, [districtId]);
|
||||
totalInserted++;
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
this.logger.error(`Voronoi FAILED for district ${districtId}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
const postCount = await this.dataSource.query(`SELECT count(*) as cnt FROM neighborhood_polygons ${country ? `WHERE country = '${country}'` : ''}`);
|
||||
return {
|
||||
status: 'success',
|
||||
country: country || 'all',
|
||||
total_polygons: parseInt(postCount[0].cnt),
|
||||
districts_processed: preDistricts.length
|
||||
};
|
||||
}
|
||||
async linkPlaces(country) {
|
||||
const tableName = `places_${country}`;
|
||||
const polyCount = await this.dataSource.query(`SELECT count(*) as cnt FROM neighborhood_polygons`);
|
||||
const placeCount = await this.dataSource.query(`SELECT count(*) as cnt FROM ${tableName} WHERE location IS NOT NULL`);
|
||||
this.logger.log(`linkPlaces pre-flight: ${polyCount[0].cnt} polygons, ${placeCount[0].cnt} places with location in ${tableName}`);
|
||||
if (parseInt(polyCount[0].cnt) === 0) {
|
||||
this.logger.error('ABORT: neighborhood_polygons is EMPTY. Run generate-voronoi first!');
|
||||
return {
|
||||
status: 'FAILED',
|
||||
error: 'neighborhood_polygons table is empty. Run generate-voronoi first.',
|
||||
polygons: 0,
|
||||
places: parseInt(placeCount[0].cnt)
|
||||
};
|
||||
}
|
||||
this.logger.log(`Linking administrative hierarchy for ${tableName} using KNN...`);
|
||||
const updateResult = await this.dataSource.query(`
|
||||
UPDATE ${tableName} p
|
||||
SET
|
||||
neighborhood_id = (
|
||||
SELECT np.id FROM neighborhood_polygons np
|
||||
WHERE (np.country = $1 OR np.country IS NULL)
|
||||
ORDER BY p.location::geometry <-> np.geometry::geometry
|
||||
LIMIT 1
|
||||
),
|
||||
sub_district_id = (
|
||||
SELECT ab.id FROM admin_boundaries ab
|
||||
WHERE ab.admin_level = 8
|
||||
AND ST_Within(p.location::geometry, ab.geom::geometry)
|
||||
LIMIT 1
|
||||
),
|
||||
district_id = (
|
||||
SELECT ab.id FROM admin_boundaries ab
|
||||
WHERE ab.admin_level = 6
|
||||
AND ST_Within(p.location::geometry, ab.geom::geometry)
|
||||
LIMIT 1
|
||||
),
|
||||
governorate_id = (
|
||||
SELECT ab.id FROM admin_boundaries ab
|
||||
WHERE ab.admin_level = 4
|
||||
AND ST_Within(p.location::geometry, ab.geom::geometry)
|
||||
LIMIT 1
|
||||
)
|
||||
WHERE p.location IS NOT NULL;
|
||||
`, [country]);
|
||||
const linkedNeighborhood = await this.dataSource.query(`SELECT count(*) as cnt FROM ${tableName} WHERE neighborhood_id IS NOT NULL`);
|
||||
const linkedDistrict = await this.dataSource.query(`SELECT count(*) as cnt FROM ${tableName} WHERE district_id IS NOT NULL`);
|
||||
const linkedGov = await this.dataSource.query(`SELECT count(*) as cnt FROM ${tableName} WHERE governorate_id IS NOT NULL`);
|
||||
const totalPlaces = await this.dataSource.query(`SELECT count(*) as cnt FROM ${tableName}`);
|
||||
const samplePlace = await this.dataSource.query(`
|
||||
SELECT p.id, p.name_ar, p.neighborhood_id, p.district_id, p.governorate_id,
|
||||
n.name_ar as neighborhood_name
|
||||
FROM ${tableName} p
|
||||
LEFT JOIN neighborhood_polygons n ON p.neighborhood_id = n.id
|
||||
WHERE p.name_ar LIKE '%معصوم%'
|
||||
LIMIT 3
|
||||
`);
|
||||
const diagnostics = {
|
||||
status: 'completed',
|
||||
country,
|
||||
total_places: parseInt(totalPlaces[0].cnt),
|
||||
linked_neighborhood: parseInt(linkedNeighborhood[0].cnt),
|
||||
linked_district: parseInt(linkedDistrict[0].cnt),
|
||||
linked_governorate: parseInt(linkedGov[0].cnt),
|
||||
sample_masoum: samplePlace,
|
||||
rows_affected: updateResult?.[1] || 'unknown'
|
||||
};
|
||||
this.logger.log(`Step 3 diagnostics: ${JSON.stringify(diagnostics)}`);
|
||||
return diagnostics;
|
||||
}
|
||||
};
|
||||
exports.AdministrativeLinkingService = AdministrativeLinkingService;
|
||||
exports.AdministrativeLinkingService = AdministrativeLinkingService = AdministrativeLinkingService_1 = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__param(1, (0, typeorm_1.InjectRepository)(neighborhood_point_entity_1.NeighborhoodPoint)),
|
||||
__param(2, (0, typeorm_1.InjectRepository)(neighborhood_polygon_entity_1.NeighborhoodPolygon)),
|
||||
__param(3, (0, typeorm_1.InjectRepository)(admin_boundary_entity_1.AdminBoundary)),
|
||||
__metadata("design:paramtypes", [typeorm_2.DataSource,
|
||||
typeorm_2.Repository,
|
||||
typeorm_2.Repository,
|
||||
typeorm_2.Repository])
|
||||
], AdministrativeLinkingService);
|
||||
//# sourceMappingURL=administrative-linking.service.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
|
||||
export declare class ReverseGeocodeDto {
|
||||
lat: number;
|
||||
lng: number;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
"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.ReverseGeocodeDto = void 0;
|
||||
const class_validator_1 = require("class-validator");
|
||||
const class_transformer_1 = require("class-transformer");
|
||||
const swagger_1 = require("@nestjs/swagger");
|
||||
class ReverseGeocodeDto {
|
||||
lat;
|
||||
lng;
|
||||
}
|
||||
exports.ReverseGeocodeDto = ReverseGeocodeDto;
|
||||
__decorate([
|
||||
(0, class_validator_1.IsNotEmpty)(),
|
||||
(0, class_validator_1.IsNumber)(),
|
||||
(0, class_validator_1.Min)(-90),
|
||||
(0, class_validator_1.Max)(90),
|
||||
(0, class_transformer_1.Type)(() => Number),
|
||||
(0, swagger_1.ApiProperty)({ description: 'Latitude' }),
|
||||
__metadata("design:type", Number)
|
||||
], ReverseGeocodeDto.prototype, "lat", void 0);
|
||||
__decorate([
|
||||
(0, class_validator_1.IsNotEmpty)(),
|
||||
(0, class_validator_1.IsNumber)(),
|
||||
(0, class_validator_1.Min)(-180),
|
||||
(0, class_validator_1.Max)(180),
|
||||
(0, class_transformer_1.Type)(() => Number),
|
||||
(0, swagger_1.ApiProperty)({ description: 'Longitude' }),
|
||||
__metadata("design:type", Number)
|
||||
], ReverseGeocodeDto.prototype, "lng", void 0);
|
||||
//# sourceMappingURL=reverse-geocode.dto.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"reverse-geocode.dto.js","sourceRoot":"","sources":["../../../src/geocoding/dto/reverse-geocode.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAiE;AACjE,yDAAyC;AACzC,6CAA8C;AAE9C,MAAa,iBAAiB;IAO5B,GAAG,CAAS;IAQZ,GAAG,CAAS;CACb;AAhBD,8CAgBC;AATC;IANC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,CAAC,EAAE,CAAC;IACR,IAAA,qBAAG,EAAC,EAAE,CAAC;IACP,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,MAAM,CAAC;IAClB,IAAA,qBAAW,EAAC,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;;8CAC7B;AAQZ;IANC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,CAAC,GAAG,CAAC;IACT,IAAA,qBAAG,EAAC,GAAG,CAAC;IACR,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,MAAM,CAAC;IAClB,IAAA,qBAAW,EAAC,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC;;8CAC9B"}
|
||||
@@ -0,0 +1,7 @@
|
||||
export declare class SearchQueryDto {
|
||||
q: string;
|
||||
lat?: number;
|
||||
lng?: number;
|
||||
radius?: number;
|
||||
country?: string;
|
||||
}
|
||||
+58
@@ -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;
|
||||
};
|
||||
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.SearchQueryDto = void 0;
|
||||
const class_validator_1 = require("class-validator");
|
||||
const class_transformer_1 = require("class-transformer");
|
||||
const swagger_1 = require("@nestjs/swagger");
|
||||
class SearchQueryDto {
|
||||
q;
|
||||
lat;
|
||||
lng;
|
||||
radius = 20000;
|
||||
country;
|
||||
}
|
||||
exports.SearchQueryDto = SearchQueryDto;
|
||||
__decorate([
|
||||
(0, class_validator_1.IsString)(),
|
||||
(0, swagger_1.ApiPropertyOptional)({ description: 'Search query string' }),
|
||||
__metadata("design:type", String)
|
||||
], SearchQueryDto.prototype, "q", void 0);
|
||||
__decorate([
|
||||
(0, class_validator_1.IsOptional)(),
|
||||
(0, class_validator_1.IsNumber)(),
|
||||
(0, class_transformer_1.Type)(() => Number),
|
||||
(0, swagger_1.ApiPropertyOptional)({ description: 'Latitude for proximity search' }),
|
||||
__metadata("design:type", Number)
|
||||
], SearchQueryDto.prototype, "lat", void 0);
|
||||
__decorate([
|
||||
(0, class_validator_1.IsOptional)(),
|
||||
(0, class_validator_1.IsNumber)(),
|
||||
(0, class_transformer_1.Type)(() => Number),
|
||||
(0, swagger_1.ApiPropertyOptional)({ description: 'Longitude for proximity search' }),
|
||||
__metadata("design:type", Number)
|
||||
], SearchQueryDto.prototype, "lng", void 0);
|
||||
__decorate([
|
||||
(0, class_validator_1.IsOptional)(),
|
||||
(0, class_validator_1.IsNumber)(),
|
||||
(0, class_validator_1.Min)(0),
|
||||
(0, class_validator_1.Max)(50000),
|
||||
(0, class_transformer_1.Type)(() => Number),
|
||||
(0, swagger_1.ApiPropertyOptional)({ description: 'Proximity radius in meters', default: 20000 }),
|
||||
__metadata("design:type", Number)
|
||||
], SearchQueryDto.prototype, "radius", void 0);
|
||||
__decorate([
|
||||
(0, class_validator_1.IsOptional)(),
|
||||
(0, class_validator_1.IsEnum)(['jordan', 'syria', 'egypt']),
|
||||
(0, swagger_1.ApiPropertyOptional)({ description: 'Country filter', enum: ['jordan', 'syria', 'egypt'] }),
|
||||
__metadata("design:type", String)
|
||||
], SearchQueryDto.prototype, "country", void 0);
|
||||
//# sourceMappingURL=search-query.dto.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"search-query.dto.js","sourceRoot":"","sources":["../../../src/geocoding/dto/search-query.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAmF;AACnF,yDAAyC;AACzC,6CAAsD;AAEtD,MAAa,cAAc;IAGzB,CAAC,CAAS;IAMV,GAAG,CAAU;IAMb,GAAG,CAAU;IAQb,MAAM,GAAY,KAAK,CAAC;IAKxB,OAAO,CAAU;CAClB;AA7BD,wCA6BC;AA1BC;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,6BAAmB,EAAC,EAAE,WAAW,EAAE,qBAAqB,EAAE,CAAC;;yCAClD;AAMV;IAJC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,MAAM,CAAC;IAClB,IAAA,6BAAmB,EAAC,EAAE,WAAW,EAAE,+BAA+B,EAAE,CAAC;;2CACzD;AAMb;IAJC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,MAAM,CAAC;IAClB,IAAA,6BAAmB,EAAC,EAAE,WAAW,EAAE,gCAAgC,EAAE,CAAC;;2CAC1D;AAQb;IANC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,CAAC,CAAC;IACN,IAAA,qBAAG,EAAC,KAAK,CAAC;IACV,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,MAAM,CAAC;IAClB,IAAA,6BAAmB,EAAC,EAAE,WAAW,EAAE,4BAA4B,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;;8CAC3D;AAKxB;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,wBAAM,EAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACpC,IAAA,6BAAmB,EAAC,EAAE,WAAW,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;;+CAC1E"}
|
||||
@@ -0,0 +1,8 @@
|
||||
export declare class AdminBoundary {
|
||||
id: number;
|
||||
country_code: string;
|
||||
admin_level: number;
|
||||
name_ar: string;
|
||||
name_en: string;
|
||||
geom: any;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
"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.AdminBoundary = void 0;
|
||||
const typeorm_1 = require("typeorm");
|
||||
let AdminBoundary = class AdminBoundary {
|
||||
id;
|
||||
country_code;
|
||||
admin_level;
|
||||
name_ar;
|
||||
name_en;
|
||||
geom;
|
||||
};
|
||||
exports.AdminBoundary = AdminBoundary;
|
||||
__decorate([
|
||||
(0, typeorm_1.PrimaryGeneratedColumn)(),
|
||||
__metadata("design:type", Number)
|
||||
], AdminBoundary.prototype, "id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ length: 3 }),
|
||||
(0, typeorm_1.Index)(),
|
||||
__metadata("design:type", String)
|
||||
], AdminBoundary.prototype, "country_code", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'int' }),
|
||||
(0, typeorm_1.Index)(),
|
||||
__metadata("design:type", Number)
|
||||
], AdminBoundary.prototype, "admin_level", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
(0, typeorm_1.Index)(),
|
||||
__metadata("design:type", String)
|
||||
], AdminBoundary.prototype, "name_ar", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
__metadata("design:type", String)
|
||||
], AdminBoundary.prototype, "name_en", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'geometry', spatialFeatureType: 'MultiPolygon', srid: 4326, nullable: true }),
|
||||
(0, typeorm_1.Index)({ spatial: true }),
|
||||
__metadata("design:type", Object)
|
||||
], AdminBoundary.prototype, "geom", void 0);
|
||||
exports.AdminBoundary = AdminBoundary = __decorate([
|
||||
(0, typeorm_1.Entity)('admin_boundaries')
|
||||
], AdminBoundary);
|
||||
//# sourceMappingURL=admin-boundary.entity.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"admin-boundary.entity.js","sourceRoot":"","sources":["../../../src/geocoding/entities/admin-boundary.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAAwE;AAGjE,IAAM,aAAa,GAAnB,MAAM,aAAa;IAExB,EAAE,CAAS;IAIX,YAAY,CAAS;IAIrB,WAAW,CAAS;IAIpB,OAAO,CAAS;IAGhB,OAAO,CAAS;IAIhB,IAAI,CAAM;CACX,CAAA;AAtBY,sCAAa;AAExB;IADC,IAAA,gCAAsB,GAAE;;yCACd;AAIX;IAFC,IAAA,gBAAM,EAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACrB,IAAA,eAAK,GAAE;;mDACa;AAIrB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACvB,IAAA,eAAK,GAAE;;kDACY;AAIpB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,eAAK,GAAE;;8CACQ;AAGhB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;8CACX;AAIhB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,UAAU,EAAE,kBAAkB,EAAE,cAAc,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC5F,IAAA,eAAK,EAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;2CACf;wBArBC,aAAa;IADzB,IAAA,gBAAM,EAAC,kBAAkB,CAAC;GACd,aAAa,CAsBzB"}
|
||||
@@ -0,0 +1,20 @@
|
||||
export declare abstract class BasePlace {
|
||||
id: number;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
name: string;
|
||||
name_ar: string;
|
||||
name_en: string;
|
||||
address: string;
|
||||
category: string;
|
||||
neighbourhood: string;
|
||||
city: string;
|
||||
description: string;
|
||||
created_at: Date;
|
||||
source: string;
|
||||
location: any;
|
||||
governorate_id: number;
|
||||
district_id: number;
|
||||
sub_district_id: number;
|
||||
neighborhood_id: number;
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
"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.BasePlace = void 0;
|
||||
const typeorm_1 = require("typeorm");
|
||||
class BasePlace {
|
||||
id;
|
||||
latitude;
|
||||
longitude;
|
||||
name;
|
||||
name_ar;
|
||||
name_en;
|
||||
address;
|
||||
category;
|
||||
neighbourhood;
|
||||
city;
|
||||
description;
|
||||
created_at;
|
||||
source;
|
||||
location;
|
||||
governorate_id;
|
||||
district_id;
|
||||
sub_district_id;
|
||||
neighborhood_id;
|
||||
}
|
||||
exports.BasePlace = BasePlace;
|
||||
__decorate([
|
||||
(0, typeorm_1.PrimaryGeneratedColumn)(),
|
||||
__metadata("design:type", Number)
|
||||
], BasePlace.prototype, "id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'decimal', precision: 10, scale: 8, nullable: true }),
|
||||
__metadata("design:type", Number)
|
||||
], BasePlace.prototype, "latitude", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'decimal', precision: 11, scale: 8, nullable: true }),
|
||||
__metadata("design:type", Number)
|
||||
], BasePlace.prototype, "longitude", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
(0, typeorm_1.Index)(),
|
||||
__metadata("design:type", String)
|
||||
], BasePlace.prototype, "name", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
(0, typeorm_1.Index)(),
|
||||
__metadata("design:type", String)
|
||||
], BasePlace.prototype, "name_ar", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
__metadata("design:type", String)
|
||||
], BasePlace.prototype, "name_en", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
__metadata("design:type", String)
|
||||
], BasePlace.prototype, "address", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
__metadata("design:type", String)
|
||||
], BasePlace.prototype, "category", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
__metadata("design:type", String)
|
||||
], BasePlace.prototype, "neighbourhood", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
__metadata("design:type", String)
|
||||
], BasePlace.prototype, "city", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'text', nullable: true }),
|
||||
__metadata("design:type", String)
|
||||
], BasePlace.prototype, "description", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.CreateDateColumn)(),
|
||||
__metadata("design:type", Date)
|
||||
], BasePlace.prototype, "created_at", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
__metadata("design:type", String)
|
||||
], BasePlace.prototype, "source", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'geometry', spatialFeatureType: 'Point', srid: 4326, nullable: true }),
|
||||
(0, typeorm_1.Index)({ spatial: true }),
|
||||
__metadata("design:type", Object)
|
||||
], BasePlace.prototype, "location", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'int', nullable: true }),
|
||||
(0, typeorm_1.Index)(),
|
||||
__metadata("design:type", Number)
|
||||
], BasePlace.prototype, "governorate_id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'int', nullable: true }),
|
||||
(0, typeorm_1.Index)(),
|
||||
__metadata("design:type", Number)
|
||||
], BasePlace.prototype, "district_id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'int', nullable: true }),
|
||||
(0, typeorm_1.Index)(),
|
||||
__metadata("design:type", Number)
|
||||
], BasePlace.prototype, "sub_district_id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'int', nullable: true }),
|
||||
(0, typeorm_1.Index)(),
|
||||
__metadata("design:type", Number)
|
||||
], BasePlace.prototype, "neighborhood_id", void 0);
|
||||
//# sourceMappingURL=base-place.entity.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"base-place.entity.js","sourceRoot":"","sources":["../../../src/geocoding/entities/base-place.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAAkF;AAElF,MAAsB,SAAS;IAE7B,EAAE,CAAS;IAGX,QAAQ,CAAS;IAGjB,SAAS,CAAS;IAIlB,IAAI,CAAS;IAIb,OAAO,CAAS;IAGhB,OAAO,CAAS;IAGhB,OAAO,CAAS;IAGhB,QAAQ,CAAS;IAGjB,aAAa,CAAS;IAGtB,IAAI,CAAS;IAGb,WAAW,CAAS;IAGpB,UAAU,CAAO;IAGjB,MAAM,CAAS;IAIf,QAAQ,CAAM;IAId,cAAc,CAAS;IAIvB,WAAW,CAAS;IAIpB,eAAe,CAAS;IAIxB,eAAe,CAAS;CACzB;AA7DD,8BA6DC;AA3DC;IADC,IAAA,gCAAsB,GAAE;;qCACd;AAGX;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2CACpD;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;4CACnD;AAIlB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,eAAK,GAAE;;uCACK;AAIb;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,eAAK,GAAE;;0CACQ;AAGhB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;0CACX;AAGhB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;0CACX;AAGhB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2CACV;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;gDACL;AAGtB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;uCACd;AAGb;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;8CACrB;AAGpB;IADC,IAAA,0BAAgB,GAAE;8BACP,IAAI;6CAAC;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;yCACZ;AAIf;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,UAAU,EAAE,kBAAkB,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACrF,IAAA,eAAK,EAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;2CACX;AAId;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACvC,IAAA,eAAK,GAAE;;iDACe;AAIvB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACvC,IAAA,eAAK,GAAE;;8CACY;AAIpB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACvC,IAAA,eAAK,GAAE;;kDACgB;AAIxB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACvC,IAAA,eAAK,GAAE;;kDACgB"}
|
||||
@@ -0,0 +1,10 @@
|
||||
export declare class NeighborhoodPoint {
|
||||
id: number;
|
||||
osm_id: number;
|
||||
name_ar: string;
|
||||
name_en: string;
|
||||
place_type: string;
|
||||
district_id: number;
|
||||
country: string;
|
||||
geometry: any;
|
||||
}
|
||||
@@ -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.NeighborhoodPoint = void 0;
|
||||
const typeorm_1 = require("typeorm");
|
||||
let NeighborhoodPoint = class NeighborhoodPoint {
|
||||
id;
|
||||
osm_id;
|
||||
name_ar;
|
||||
name_en;
|
||||
place_type;
|
||||
district_id;
|
||||
country;
|
||||
geometry;
|
||||
};
|
||||
exports.NeighborhoodPoint = NeighborhoodPoint;
|
||||
__decorate([
|
||||
(0, typeorm_1.PrimaryGeneratedColumn)(),
|
||||
__metadata("design:type", Number)
|
||||
], NeighborhoodPoint.prototype, "id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'bigint', unique: true, nullable: true }),
|
||||
(0, typeorm_1.Index)(),
|
||||
__metadata("design:type", Number)
|
||||
], NeighborhoodPoint.prototype, "osm_id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
(0, typeorm_1.Index)(),
|
||||
__metadata("design:type", String)
|
||||
], NeighborhoodPoint.prototype, "name_ar", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
__metadata("design:type", String)
|
||||
], NeighborhoodPoint.prototype, "name_en", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
__metadata("design:type", String)
|
||||
], NeighborhoodPoint.prototype, "place_type", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'int', nullable: true }),
|
||||
(0, typeorm_1.Index)(),
|
||||
__metadata("design:type", Number)
|
||||
], NeighborhoodPoint.prototype, "district_id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
(0, typeorm_1.Index)(),
|
||||
__metadata("design:type", String)
|
||||
], NeighborhoodPoint.prototype, "country", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'geometry', spatialFeatureType: 'Point', srid: 4326 }),
|
||||
(0, typeorm_1.Index)({ spatial: true }),
|
||||
__metadata("design:type", Object)
|
||||
], NeighborhoodPoint.prototype, "geometry", void 0);
|
||||
exports.NeighborhoodPoint = NeighborhoodPoint = __decorate([
|
||||
(0, typeorm_1.Entity)('neighborhood_points')
|
||||
], NeighborhoodPoint);
|
||||
//# sourceMappingURL=neighborhood-point.entity.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"neighborhood-point.entity.js","sourceRoot":"","sources":["../../../src/geocoding/entities/neighborhood-point.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAAwE;AAGjE,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAE5B,EAAE,CAAS;IAIX,MAAM,CAAS;IAIf,OAAO,CAAS;IAGhB,OAAO,CAAS;IAGhB,UAAU,CAAS;IAInB,WAAW,CAAS;IAIpB,OAAO,CAAS;IAIhB,QAAQ,CAAM;CACf,CAAA;AA7BY,8CAAiB;AAE5B;IADC,IAAA,gCAAsB,GAAE;;6CACd;AAIX;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACxD,IAAA,eAAK,GAAE;;iDACO;AAIf;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,eAAK,GAAE;;kDACQ;AAGhB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;kDACX;AAGhB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;qDACR;AAInB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACvC,IAAA,eAAK,GAAE;;sDACY;AAIpB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,eAAK,GAAE;;kDACQ;AAIhB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,UAAU,EAAE,kBAAkB,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACrE,IAAA,eAAK,EAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;mDACX;4BA5BH,iBAAiB;IAD7B,IAAA,gBAAM,EAAC,qBAAqB,CAAC;GACjB,iBAAiB,CA6B7B"}
|
||||
@@ -0,0 +1,12 @@
|
||||
export declare class NeighborhoodPolygon {
|
||||
id: number;
|
||||
osm_id: number;
|
||||
name_ar: string;
|
||||
name_en: string;
|
||||
place_type: string;
|
||||
parent_id: number;
|
||||
method: string;
|
||||
country: string;
|
||||
confidence: number;
|
||||
geometry: any;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
"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.NeighborhoodPolygon = void 0;
|
||||
const typeorm_1 = require("typeorm");
|
||||
let NeighborhoodPolygon = class NeighborhoodPolygon {
|
||||
id;
|
||||
osm_id;
|
||||
name_ar;
|
||||
name_en;
|
||||
place_type;
|
||||
parent_id;
|
||||
method;
|
||||
country;
|
||||
confidence;
|
||||
geometry;
|
||||
};
|
||||
exports.NeighborhoodPolygon = NeighborhoodPolygon;
|
||||
__decorate([
|
||||
(0, typeorm_1.PrimaryGeneratedColumn)(),
|
||||
__metadata("design:type", Number)
|
||||
], NeighborhoodPolygon.prototype, "id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'bigint', nullable: true }),
|
||||
(0, typeorm_1.Index)(),
|
||||
__metadata("design:type", Number)
|
||||
], NeighborhoodPolygon.prototype, "osm_id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
(0, typeorm_1.Index)(),
|
||||
__metadata("design:type", String)
|
||||
], NeighborhoodPolygon.prototype, "name_ar", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
__metadata("design:type", String)
|
||||
], NeighborhoodPolygon.prototype, "name_en", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
__metadata("design:type", String)
|
||||
], NeighborhoodPolygon.prototype, "place_type", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'int', nullable: true }),
|
||||
(0, typeorm_1.Index)(),
|
||||
__metadata("design:type", Number)
|
||||
], NeighborhoodPolygon.prototype, "parent_id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ default: 'voronoi' }),
|
||||
__metadata("design:type", String)
|
||||
], NeighborhoodPolygon.prototype, "method", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
(0, typeorm_1.Index)(),
|
||||
__metadata("design:type", String)
|
||||
], NeighborhoodPolygon.prototype, "country", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'float', default: 0.7 }),
|
||||
__metadata("design:type", Number)
|
||||
], NeighborhoodPolygon.prototype, "confidence", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'geometry', spatialFeatureType: 'MultiPolygon', srid: 4326 }),
|
||||
(0, typeorm_1.Index)({ spatial: true }),
|
||||
__metadata("design:type", Object)
|
||||
], NeighborhoodPolygon.prototype, "geometry", void 0);
|
||||
exports.NeighborhoodPolygon = NeighborhoodPolygon = __decorate([
|
||||
(0, typeorm_1.Entity)('neighborhood_polygons')
|
||||
], NeighborhoodPolygon);
|
||||
//# sourceMappingURL=neighborhood-polygon.entity.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"neighborhood-polygon.entity.js","sourceRoot":"","sources":["../../../src/geocoding/entities/neighborhood-polygon.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAAwE;AAGjE,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAE9B,EAAE,CAAS;IAIX,MAAM,CAAS;IAIf,OAAO,CAAS;IAGhB,OAAO,CAAS;IAGhB,UAAU,CAAS;IAInB,SAAS,CAAS;IAGlB,MAAM,CAAS;IAIf,OAAO,CAAS;IAGhB,UAAU,CAAS;IAInB,QAAQ,CAAM;CACf,CAAA;AAnCY,kDAAmB;AAE9B;IADC,IAAA,gCAAsB,GAAE;;+CACd;AAIX;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1C,IAAA,eAAK,GAAE;;mDACO;AAIf;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,eAAK,GAAE;;oDACQ;AAGhB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;oDACX;AAGhB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;uDACR;AAInB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACvC,IAAA,eAAK,GAAE;;sDACU;AAGlB;IADC,IAAA,gBAAM,EAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;;mDAChB;AAIf;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,eAAK,GAAE;;oDACQ;AAGhB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;;uDACrB;AAInB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,UAAU,EAAE,kBAAkB,EAAE,cAAc,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC5E,IAAA,eAAK,EAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;qDACX;8BAlCH,mBAAmB;IAD/B,IAAA,gBAAM,EAAC,uBAAuB,CAAC;GACnB,mBAAmB,CAmC/B"}
|
||||
@@ -4,6 +4,7 @@ export declare class OsmPointWithArea {
|
||||
latitude: number;
|
||||
name: string;
|
||||
name_ar: string;
|
||||
name_en: string;
|
||||
amenity: string;
|
||||
shop: string;
|
||||
addr_street: string;
|
||||
|
||||
@@ -17,6 +17,7 @@ let OsmPointWithArea = class OsmPointWithArea {
|
||||
latitude;
|
||||
name;
|
||||
name_ar;
|
||||
name_en;
|
||||
amenity;
|
||||
shop;
|
||||
addr_street;
|
||||
@@ -46,6 +47,10 @@ __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, "name_en", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ nullable: true }),
|
||||
__metadata("design:type", String)
|
||||
|
||||
@@ -1 +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"}
|
||||
{"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,OAAO,CAAS;IAGhB,IAAI,CAAS;IAGb,WAAW,CAAS;IAGpB,kBAAkB,CAAS;IAG3B,SAAS,CAAS;IAGlB,UAAU,CAAS;IAGnB,IAAI,CAAM;CACX,CAAA;AAvCY,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;;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;2BAtCC,gBAAgB;IAD5B,IAAA,gBAAM,EAAC,sBAAsB,CAAC;GAClB,gBAAgB,CAuC5B"}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { BasePlace } from './base-place.entity';
|
||||
export declare class PlaceEgypt extends BasePlace {
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
"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.PlaceEgypt = void 0;
|
||||
const typeorm_1 = require("typeorm");
|
||||
const base_place_entity_1 = require("./base-place.entity");
|
||||
let PlaceEgypt = class PlaceEgypt extends base_place_entity_1.BasePlace {
|
||||
};
|
||||
exports.PlaceEgypt = PlaceEgypt;
|
||||
exports.PlaceEgypt = PlaceEgypt = __decorate([
|
||||
(0, typeorm_1.Entity)('places_egypt')
|
||||
], PlaceEgypt);
|
||||
//# sourceMappingURL=place-egypt.entity.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"place-egypt.entity.js","sourceRoot":"","sources":["../../../src/geocoding/entities/place-egypt.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,qCAAiC;AACjC,2DAAgD;AAGzC,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,6BAAS;CAAG,CAAA;AAA/B,gCAAU;qBAAV,UAAU;IADtB,IAAA,gBAAM,EAAC,cAAc,CAAC;GACV,UAAU,CAAqB"}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { BasePlace } from './base-place.entity';
|
||||
export declare class PlaceJordan extends BasePlace {
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
"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.PlaceJordan = void 0;
|
||||
const typeorm_1 = require("typeorm");
|
||||
const base_place_entity_1 = require("./base-place.entity");
|
||||
let PlaceJordan = class PlaceJordan extends base_place_entity_1.BasePlace {
|
||||
};
|
||||
exports.PlaceJordan = PlaceJordan;
|
||||
exports.PlaceJordan = PlaceJordan = __decorate([
|
||||
(0, typeorm_1.Entity)('places_jordan')
|
||||
], PlaceJordan);
|
||||
//# sourceMappingURL=place-jordan.entity.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"place-jordan.entity.js","sourceRoot":"","sources":["../../../src/geocoding/entities/place-jordan.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,qCAAiC;AACjC,2DAAgD;AAGzC,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,6BAAS;CAAG,CAAA;AAAhC,kCAAW;sBAAX,WAAW;IADvB,IAAA,gBAAM,EAAC,eAAe,CAAC;GACX,WAAW,CAAqB"}
|
||||
+2
-11
@@ -1,12 +1,3 @@
|
||||
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;
|
||||
import { BasePlace } from './base-place.entity';
|
||||
export declare class PlaceSyria extends BasePlace {
|
||||
}
|
||||
|
||||
+2
-54
@@ -5,65 +5,13 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
||||
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;
|
||||
const base_place_entity_1 = require("./base-place.entity");
|
||||
let PlaceSyria = class PlaceSyria extends base_place_entity_1.BasePlace {
|
||||
};
|
||||
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);
|
||||
|
||||
@@ -1 +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"}
|
||||
{"version":3,"file":"place-syria.entity.js","sourceRoot":"","sources":["../../../src/geocoding/entities/place-syria.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,qCAAiC;AACjC,2DAAgD;AAGzC,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,6BAAS;CAAG,CAAA;AAA/B,gCAAU;qBAAV,UAAU;IADtB,IAAA,gBAAM,EAAC,cAAc,CAAC;GACV,UAAU,CAAqB"}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { OnModuleInit } from '@nestjs/common';
|
||||
import { Repository } from 'typeorm';
|
||||
import { PlaceSyria } from './entities/place-syria.entity';
|
||||
export declare class GeocodingInitService implements OnModuleInit {
|
||||
private readonly repo;
|
||||
private readonly logger;
|
||||
constructor(repo: Repository<PlaceSyria>);
|
||||
onModuleInit(): Promise<void>;
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
"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 GeocodingInitService_1;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.GeocodingInitService = 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");
|
||||
let GeocodingInitService = GeocodingInitService_1 = class GeocodingInitService {
|
||||
repo;
|
||||
logger = new common_1.Logger(GeocodingInitService_1.name);
|
||||
constructor(repo) {
|
||||
this.repo = repo;
|
||||
}
|
||||
async onModuleInit() {
|
||||
this.logger.log('Checking for PostGIS extensions and triggers...');
|
||||
try {
|
||||
await this.repo.query('CREATE EXTENSION IF NOT EXISTS postgis;');
|
||||
await this.repo.query('CREATE EXTENSION IF NOT EXISTS pg_trgm;');
|
||||
const tables = ['places_jordan', 'places_syria', 'places_egypt'];
|
||||
const columnMapping = [
|
||||
{ old: 'admin_level4_id', new: 'governorate_id' },
|
||||
{ old: 'admin_level6_id', new: 'district_id' },
|
||||
{ old: 'admin_level8_id', new: 'sub_district_id' },
|
||||
{ old: 'admin_level10_id', new: 'neighborhood_id' }
|
||||
];
|
||||
for (const table of tables) {
|
||||
for (const mapping of columnMapping) {
|
||||
await this.repo.query(`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = '${table}' AND column_name = '${mapping.old}') THEN
|
||||
ALTER TABLE ${table} RENAME COLUMN ${mapping.old} TO ${mapping.new};
|
||||
END IF;
|
||||
END $$;
|
||||
`);
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
-- Keep standard admin boundary logic for higher levels
|
||||
NEW.governorate_id := (SELECT id FROM admin_boundaries WHERE admin_level = 4 AND ST_Contains(geom, NEW.location) LIMIT 1);
|
||||
NEW.district_id := (SELECT id FROM admin_boundaries WHERE admin_level = 6 AND ST_Contains(geom, NEW.location) LIMIT 1);
|
||||
NEW.sub_district_id := (SELECT id FROM admin_boundaries WHERE admin_level = 8 AND ST_Contains(geom, NEW.location) LIMIT 1);
|
||||
|
||||
-- ROOT CAUSE FIX: Use the new Voronoi polygons for neighborhoods!
|
||||
NEW.neighborhood_id := (SELECT id FROM neighborhood_polygons ORDER BY NEW.location::geometry <-> geometry::geometry LIMIT 1);
|
||||
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();
|
||||
`);
|
||||
await this.repo.query(`
|
||||
DROP TRIGGER IF EXISTS trg_sync_place_location_jordan ON places_jordan;
|
||||
CREATE TRIGGER trg_sync_place_location_jordan
|
||||
BEFORE INSERT OR UPDATE ON places_jordan
|
||||
FOR EACH ROW EXECUTE FUNCTION sync_place_location();
|
||||
`);
|
||||
await this.repo.query(`
|
||||
DROP TRIGGER IF EXISTS trg_sync_place_location_egypt ON places_egypt;
|
||||
CREATE TRIGGER trg_sync_place_location_egypt
|
||||
BEFORE INSERT OR UPDATE ON places_egypt
|
||||
FOR EACH ROW EXECUTE FUNCTION sync_place_location();
|
||||
`);
|
||||
await this.repo.query('CREATE INDEX IF NOT EXISTS idx_places_jordan_location ON places_jordan USING gist (location);');
|
||||
await this.repo.query('CREATE INDEX IF NOT EXISTS idx_places_egypt_location ON places_egypt USING gist (location);');
|
||||
await this.repo.query('CREATE INDEX IF NOT EXISTS idx_places_jordan_names_trgm ON places_jordan USING gist (name_ar gist_trgm_ops, name_en gist_trgm_ops);');
|
||||
await this.repo.query('CREATE INDEX IF NOT EXISTS idx_places_egypt_names_trgm ON places_egypt USING gist (name_ar gist_trgm_ops, name_en gist_trgm_ops);');
|
||||
this.logger.log('Geocoding database triggers and optimized indexes initialized for Syria, Jordan, and Egypt.');
|
||||
}
|
||||
catch (err) {
|
||||
this.logger.error('Failed to initialize database geocoding triggers:', err);
|
||||
}
|
||||
}
|
||||
};
|
||||
exports.GeocodingInitService = GeocodingInitService;
|
||||
exports.GeocodingInitService = GeocodingInitService = GeocodingInitService_1 = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__param(0, (0, typeorm_1.InjectRepository)(place_syria_entity_1.PlaceSyria)),
|
||||
__metadata("design:paramtypes", [typeorm_2.Repository])
|
||||
], GeocodingInitService);
|
||||
//# sourceMappingURL=geocoding-init.service.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"geocoding-init.service.js","sourceRoot":"","sources":["../../src/geocoding/geocoding-init.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAkE;AAClE,6CAAmD;AACnD,qCAAqC;AACrC,sEAA2D;AAGpD,IAAM,oBAAoB,4BAA1B,MAAM,oBAAoB;IAKZ;IAJF,MAAM,GAAG,IAAI,eAAM,CAAC,sBAAoB,CAAC,IAAI,CAAC,CAAC;IAEhE,YAEmB,IAA4B;QAA5B,SAAI,GAAJ,IAAI,CAAwB;IAC5C,CAAC;IAEJ,KAAK,CAAC,YAAY;QAChB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;QACnE,IAAI,CAAC;YAEH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;YACjE,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;YAGjE,MAAM,MAAM,GAAG,CAAC,eAAe,EAAE,cAAc,EAAE,cAAc,CAAC,CAAC;YACjE,MAAM,aAAa,GAAG;gBACpB,EAAE,GAAG,EAAE,iBAAiB,EAAE,GAAG,EAAE,gBAAgB,EAAE;gBACjD,EAAE,GAAG,EAAE,iBAAiB,EAAE,GAAG,EAAE,aAAa,EAAE;gBAC9C,EAAE,GAAG,EAAE,iBAAiB,EAAE,GAAG,EAAE,iBAAiB,EAAE;gBAClD,EAAE,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,iBAAiB,EAAE;aACpD,CAAC;YAEF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;oBACpC,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;;;wFAGwD,KAAK,wBAAwB,OAAO,CAAC,GAAG;8BAClG,KAAK,kBAAkB,OAAO,CAAC,GAAG,OAAO,OAAO,CAAC,GAAG;;;WAGvE,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YAGD,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;OAiBrB,CAAC,CAAC;YAEH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;;;;;OAKrB,CAAC,CAAC;YAGH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;;;;;OAKrB,CAAC,CAAC;YAEH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;;;;;OAKrB,CAAC,CAAC;YAGH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,+FAA+F,CAAC,CAAC;YACvH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,6FAA6F,CAAC,CAAC;YAGrH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,qIAAqI,CAAC,CAAC;YAC7J,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,mIAAmI,CAAC,CAAC;YAE3J,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,6FAA6F,CAAC,CAAC;QACjH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,mDAAmD,EAAE,GAAG,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;CACF,CAAA;AA5FY,oDAAoB;+BAApB,oBAAoB;IADhC,IAAA,mBAAU,GAAE;IAKR,WAAA,IAAA,0BAAgB,EAAC,+BAAU,CAAC,CAAA;qCACN,oBAAU;GALxB,oBAAoB,CA4FhC"}
|
||||
+104
-6
@@ -1,11 +1,109 @@
|
||||
import { GeocodingService } from './geocoding.service';
|
||||
import { AdminBoundariesService } from './admin-boundaries.service';
|
||||
import { JordanResearchService } from './jordan-research.service';
|
||||
import { AdministrativeLinkingService } from './administrative-linking.service';
|
||||
import { SearchQueryDto } from './dto/search-query.dto';
|
||||
import { ReverseGeocodeDto } from './dto/reverse-geocode.dto';
|
||||
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[];
|
||||
private readonly adminBoundariesService;
|
||||
private readonly jordanResearchService;
|
||||
private readonly adminLinkingService;
|
||||
constructor(geocodingService: GeocodingService, adminBoundariesService: AdminBoundariesService, jordanResearchService: JordanResearchService, adminLinkingService: AdministrativeLinkingService);
|
||||
search(queryDto: SearchQueryDto): Promise<{
|
||||
results: any;
|
||||
source: string;
|
||||
} | {
|
||||
results: any[];
|
||||
source?: undefined;
|
||||
}>;
|
||||
reverse(reverseDto: ReverseGeocodeDto): Promise<any>;
|
||||
addPlace(placeData: any): Promise<{
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
location: string;
|
||||
id: number;
|
||||
name: string;
|
||||
name_ar: string;
|
||||
name_en: string;
|
||||
address: string;
|
||||
category: string;
|
||||
neighbourhood: string;
|
||||
city: string;
|
||||
description: string;
|
||||
created_at: Date;
|
||||
source: string;
|
||||
governorate_id: number;
|
||||
district_id: number;
|
||||
sub_district_id: number;
|
||||
neighborhood_id: number;
|
||||
}>;
|
||||
deletePlace(country: string, name?: string, id?: string): Promise<{
|
||||
success: boolean;
|
||||
affected: number | null | undefined;
|
||||
}>;
|
||||
upsertPlace(placeData: any): Promise<{
|
||||
id: any;
|
||||
action: string;
|
||||
}>;
|
||||
upsertBatch(body: {
|
||||
places: any[];
|
||||
}): Promise<{
|
||||
total: number;
|
||||
processed: number;
|
||||
created: number;
|
||||
updated: number;
|
||||
}>;
|
||||
getPlaces(limit?: string): Promise<(import("./entities/place-syria.entity").PlaceSyria | import("./entities/place-jordan.entity").PlaceJordan | import("./entities/place-egypt.entity").PlaceEgypt)[]>;
|
||||
getGeoJSON(): Promise<{
|
||||
type: string;
|
||||
features: any;
|
||||
}>;
|
||||
importBoundaries(country: string, filePath: string): Promise<{
|
||||
success: boolean;
|
||||
imported: number;
|
||||
errors: number;
|
||||
skipped: number;
|
||||
}>;
|
||||
zarqaResearch(): Promise<{
|
||||
area: string;
|
||||
report_timestamp: string;
|
||||
sources: {
|
||||
osm: any;
|
||||
overture: any;
|
||||
wikidata: any;
|
||||
static_files: {};
|
||||
hdx: string;
|
||||
gadm: string;
|
||||
};
|
||||
comparison_summary: string;
|
||||
}>;
|
||||
syncNeighborhoods(bbox?: string, country?: string): Promise<{
|
||||
detected_country: string;
|
||||
osm_fetched: any;
|
||||
country_points: number;
|
||||
total_points_in_db: number;
|
||||
linked_to_district: number;
|
||||
}>;
|
||||
generateVoronoi(country?: string): Promise<{
|
||||
status: string;
|
||||
country: string;
|
||||
total_polygons: number;
|
||||
districts_processed: any;
|
||||
}>;
|
||||
linkPlaces(country: 'jordan' | 'syria' | 'egypt'): Promise<{
|
||||
status: string;
|
||||
country: "syria" | "jordan" | "egypt";
|
||||
total_places: number;
|
||||
linked_neighborhood: number;
|
||||
linked_district: number;
|
||||
linked_governorate: number;
|
||||
sample_masoum: any;
|
||||
rows_affected: any;
|
||||
} | {
|
||||
status: string;
|
||||
error: string;
|
||||
polygons: number;
|
||||
places: number;
|
||||
}>;
|
||||
reverse(lat: number, lng: number): Promise<any>;
|
||||
addPlace(placeData: any): Promise<import("./entities/place-syria.entity").PlaceSyria>;
|
||||
}
|
||||
|
||||
+155
-14
@@ -16,41 +16,87 @@ exports.GeocodingController = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const swagger_1 = require("@nestjs/swagger");
|
||||
const geocoding_service_1 = require("./geocoding.service");
|
||||
const admin_boundaries_service_1 = require("./admin-boundaries.service");
|
||||
const jordan_research_service_1 = require("./jordan-research.service");
|
||||
const administrative_linking_service_1 = require("./administrative-linking.service");
|
||||
const api_key_guard_1 = require("../common/guards/api-key.guard");
|
||||
const rate_limiter_guard_1 = require("../common/guards/rate-limiter.guard");
|
||||
const search_query_dto_1 = require("./dto/search-query.dto");
|
||||
const reverse_geocode_dto_1 = require("./dto/reverse-geocode.dto");
|
||||
let GeocodingController = class GeocodingController {
|
||||
geocodingService;
|
||||
constructor(geocodingService) {
|
||||
adminBoundariesService;
|
||||
jordanResearchService;
|
||||
adminLinkingService;
|
||||
constructor(geocodingService, adminBoundariesService, jordanResearchService, adminLinkingService) {
|
||||
this.geocodingService = geocodingService;
|
||||
this.adminBoundariesService = adminBoundariesService;
|
||||
this.jordanResearchService = jordanResearchService;
|
||||
this.adminLinkingService = adminLinkingService;
|
||||
}
|
||||
async search(query) {
|
||||
return this.geocodingService.searchPlaces(query);
|
||||
async search(queryDto) {
|
||||
const { q, lat, lng, radius, country } = queryDto;
|
||||
return this.geocodingService.searchPlaces(q, lat, lng, radius, country);
|
||||
}
|
||||
async reverse(lat, lng) {
|
||||
async reverse(reverseDto) {
|
||||
const { lat, lng } = reverseDto;
|
||||
return this.geocodingService.reverseGeocode(lat, lng);
|
||||
}
|
||||
async addPlace(placeData) {
|
||||
return this.geocodingService.addPlace(placeData);
|
||||
}
|
||||
async deletePlace(country, name, id) {
|
||||
if (id) {
|
||||
return this.geocodingService.deletePlaceById(Number(id), country);
|
||||
}
|
||||
if (name) {
|
||||
return this.geocodingService.deletePlacesByName(name, country);
|
||||
}
|
||||
throw new common_1.HttpException('Name or ID required', common_1.HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
async upsertPlace(placeData) {
|
||||
return this.geocodingService.upsertPlace(placeData);
|
||||
}
|
||||
async upsertBatch(body) {
|
||||
return this.geocodingService.upsertBatch(body.places);
|
||||
}
|
||||
async getPlaces(limit) {
|
||||
return this.geocodingService.getRecentPlaces(limit ? parseInt(limit, 10) : 50);
|
||||
}
|
||||
async getGeoJSON() {
|
||||
return this.geocodingService.getAllPlacesGeoJSON();
|
||||
}
|
||||
async importBoundaries(country, filePath) {
|
||||
return this.adminBoundariesService.importFromFile(country, filePath);
|
||||
}
|
||||
async zarqaResearch() {
|
||||
return this.jordanResearchService.generateZarqaReport();
|
||||
}
|
||||
async syncNeighborhoods(bbox, country) {
|
||||
return this.adminLinkingService.syncOsmNeighborhoodPoints(bbox, country);
|
||||
}
|
||||
async generateVoronoi(country) {
|
||||
return this.adminLinkingService.generateVoronoiNeighborhoods(country);
|
||||
}
|
||||
async linkPlaces(country) {
|
||||
return this.adminLinkingService.linkPlaces(country);
|
||||
}
|
||||
};
|
||||
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')),
|
||||
__param(0, (0, common_1.Query)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String]),
|
||||
__metadata("design:paramtypes", [search_query_dto_1.SearchQueryDto]),
|
||||
__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')),
|
||||
__param(0, (0, common_1.Query)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Number, Number]),
|
||||
__metadata("design:paramtypes", [reverse_geocode_dto_1.ReverseGeocodeDto]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], GeocodingController.prototype, "reverse", null);
|
||||
__decorate([
|
||||
@@ -61,10 +107,105 @@ __decorate([
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], GeocodingController.prototype, "addPlace", null);
|
||||
__decorate([
|
||||
(0, common_1.Delete)('places'),
|
||||
(0, swagger_1.ApiOperation)({ summary: 'Delete a place by name or ID' }),
|
||||
(0, swagger_1.ApiQuery)({ name: 'name', required: false }),
|
||||
(0, swagger_1.ApiQuery)({ name: 'id', required: false, type: Number }),
|
||||
(0, swagger_1.ApiQuery)({ name: 'country', required: true, enum: ['jordan', 'syria', 'egypt'] }),
|
||||
__param(0, (0, common_1.Query)('country')),
|
||||
__param(1, (0, common_1.Query)('name')),
|
||||
__param(2, (0, common_1.Query)('id')),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String, String, String]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], GeocodingController.prototype, "deletePlace", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('upsert-place'),
|
||||
(0, swagger_1.ApiOperation)({ summary: 'Add or Update a location (Automated Scraper)' }),
|
||||
__param(0, (0, common_1.Body)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], GeocodingController.prototype, "upsertPlace", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('upsert-batch'),
|
||||
(0, swagger_1.ApiOperation)({ summary: 'Add or Update multiple locations in bulk' }),
|
||||
__param(0, (0, common_1.Body)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], GeocodingController.prototype, "upsertBatch", null);
|
||||
__decorate([
|
||||
(0, common_1.Get)('places'),
|
||||
(0, swagger_1.ApiOperation)({ summary: 'Get recent user submitted places' }),
|
||||
__param(0, (0, common_1.Query)('limit')),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], GeocodingController.prototype, "getPlaces", null);
|
||||
__decorate([
|
||||
(0, common_1.Get)('geojson'),
|
||||
(0, swagger_1.ApiOperation)({ summary: 'Get all user submitted places as GeoJSON for Map Style' }),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:returntype", Promise)
|
||||
], GeocodingController.prototype, "getGeoJSON", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('import-boundaries'),
|
||||
(0, swagger_1.ApiOperation)({ summary: 'Import administrative boundaries from a local GeoJSON file on the server' }),
|
||||
(0, swagger_1.ApiQuery)({ name: 'country', required: true }),
|
||||
(0, swagger_1.ApiQuery)({ name: 'filePath', required: true }),
|
||||
__param(0, (0, common_1.Query)('country')),
|
||||
__param(1, (0, common_1.Query)('filePath')),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String, String]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], GeocodingController.prototype, "importBoundaries", null);
|
||||
__decorate([
|
||||
(0, common_1.Get)('research/zarqa'),
|
||||
(0, swagger_1.ApiOperation)({ summary: 'Generate a research report for Zarqa, Jordan (Sample Data)' }),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:returntype", Promise)
|
||||
], GeocodingController.prototype, "zarqaResearch", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('admin/sync-neighborhoods'),
|
||||
(0, swagger_1.ApiOperation)({ summary: 'Sync neighborhood points from OSM for a bbox' }),
|
||||
(0, swagger_1.ApiQuery)({ name: 'bbox', required: false }),
|
||||
(0, swagger_1.ApiQuery)({ name: 'country', required: false, enum: ['jordan', 'syria', 'egypt'] }),
|
||||
__param(0, (0, common_1.Query)('bbox')),
|
||||
__param(1, (0, common_1.Query)('country')),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String, String]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], GeocodingController.prototype, "syncNeighborhoods", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('admin/generate-voronoi'),
|
||||
(0, swagger_1.ApiOperation)({ summary: 'Generate Voronoi polygons for neighborhoods' }),
|
||||
(0, swagger_1.ApiQuery)({ name: 'country', required: false, enum: ['jordan', 'syria', 'egypt'] }),
|
||||
__param(0, (0, common_1.Query)('country')),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], GeocodingController.prototype, "generateVoronoi", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('admin/link-places'),
|
||||
(0, swagger_1.ApiOperation)({ summary: 'Link places to administrative hierarchy' }),
|
||||
(0, swagger_1.ApiQuery)({ name: 'country', required: true, enum: ['jordan', 'syria', 'egypt'] }),
|
||||
__param(0, (0, common_1.Query)('country')),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], GeocodingController.prototype, "linkPlaces", null);
|
||||
exports.GeocodingController = GeocodingController = __decorate([
|
||||
(0, swagger_1.ApiTags)('geocoding'),
|
||||
(0, swagger_1.ApiHeader)({ name: 'x-api-key', description: 'Multi-tenant API Key', required: true }),
|
||||
(0, common_1.Controller)('geocoding'),
|
||||
(0, common_1.UseGuards)(api_key_guard_1.ApiKeyGuard),
|
||||
__metadata("design:paramtypes", [geocoding_service_1.GeocodingService])
|
||||
(0, common_1.UseGuards)(api_key_guard_1.ApiKeyGuard, rate_limiter_guard_1.TenantThrottlerGuard),
|
||||
__metadata("design:paramtypes", [geocoding_service_1.GeocodingService,
|
||||
admin_boundaries_service_1.AdminBoundariesService,
|
||||
jordan_research_service_1.JordanResearchService,
|
||||
administrative_linking_service_1.AdministrativeLinkingService])
|
||||
], GeocodingController);
|
||||
//# sourceMappingURL=geocoding.controller.js.map
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+65
-3
@@ -1,19 +1,63 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
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 __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
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_init_service_1 = require("./geocoding-init.service");
|
||||
const geocoding_controller_1 = require("./geocoding.controller");
|
||||
const place_syria_entity_1 = require("./entities/place-syria.entity");
|
||||
const place_jordan_entity_1 = require("./entities/place-jordan.entity");
|
||||
const place_egypt_entity_1 = require("./entities/place-egypt.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");
|
||||
const admin_boundary_entity_1 = require("./entities/admin-boundary.entity");
|
||||
const admin_boundaries_service_1 = require("./admin-boundaries.service");
|
||||
const jordan_research_service_1 = require("./jordan-research.service");
|
||||
const administrative_linking_service_1 = require("./administrative-linking.service");
|
||||
const neighborhood_point_entity_1 = require("./entities/neighborhood-point.entity");
|
||||
const neighborhood_polygon_entity_1 = require("./entities/neighborhood-polygon.entity");
|
||||
const cache_manager_1 = require("@nestjs/cache-manager");
|
||||
const redisStore = __importStar(require("cache-manager-redis-store"));
|
||||
let GeocodingModule = class GeocodingModule {
|
||||
};
|
||||
exports.GeocodingModule = GeocodingModule;
|
||||
@@ -22,12 +66,30 @@ exports.GeocodingModule = GeocodingModule = __decorate([
|
||||
imports: [
|
||||
typeorm_1.TypeOrmModule.forFeature([
|
||||
place_syria_entity_1.PlaceSyria,
|
||||
place_jordan_entity_1.PlaceJordan,
|
||||
place_egypt_entity_1.PlaceEgypt,
|
||||
osm_area_entity_1.OsmArea,
|
||||
osm_point_with_area_entity_1.OsmPointWithArea
|
||||
], 'mysqlConnection'),
|
||||
osm_point_with_area_entity_1.OsmPointWithArea,
|
||||
admin_boundary_entity_1.AdminBoundary,
|
||||
neighborhood_point_entity_1.NeighborhoodPoint,
|
||||
neighborhood_polygon_entity_1.NeighborhoodPolygon
|
||||
]),
|
||||
cache_manager_1.CacheModule.register({
|
||||
store: redisStore,
|
||||
host: process.env.REDIS_HOST || 'localhost',
|
||||
port: parseInt(process.env.REDIS_PORT || '6379', 10),
|
||||
ttl: 3600,
|
||||
}),
|
||||
],
|
||||
controllers: [geocoding_controller_1.GeocodingController],
|
||||
providers: [geocoding_service_1.GeocodingService],
|
||||
providers: [
|
||||
geocoding_service_1.GeocodingService,
|
||||
geocoding_init_service_1.GeocodingInitService,
|
||||
admin_boundaries_service_1.AdminBoundariesService,
|
||||
jordan_research_service_1.JordanResearchService,
|
||||
administrative_linking_service_1.AdministrativeLinkingService
|
||||
],
|
||||
exports: [geocoding_service_1.GeocodingService],
|
||||
})
|
||||
], GeocodingModule);
|
||||
//# sourceMappingURL=geocoding.module.js.map
|
||||
+1
-1
@@ -1 +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"}
|
||||
{"version":3,"file":"geocoding.module.js","sourceRoot":"","sources":["../../src/geocoding/geocoding.module.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAAwC;AACxC,6CAAgD;AAChD,2DAAuD;AACvD,qEAAgE;AAChE,iEAA6D;AAC7D,sEAA2D;AAC3D,wEAA6D;AAC7D,sEAA2D;AAC3D,gEAAqD;AACrD,sFAAyE;AACzE,4EAAiE;AACjE,yEAAoE;AACpE,uEAAkE;AAClE,qFAAgF;AAChF,oFAAyE;AACzE,wFAA6E;AAC7E,yDAAoD;AACpD,sEAAwD;AA+BjD,IAAM,eAAe,GAArB,MAAM,eAAe;CAAG,CAAA;AAAlB,0CAAe;0BAAf,eAAe;IA7B3B,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,uBAAa,CAAC,UAAU,CAAC;gBACvB,+BAAU;gBACV,iCAAW;gBACX,+BAAU;gBACV,yBAAO;gBACP,6CAAgB;gBAChB,qCAAa;gBACb,6CAAiB;gBACjB,iDAAmB;aACpB,CAAC;YACF,2BAAW,CAAC,QAAQ,CAAC;gBACnB,KAAK,EAAE,UAAU;gBACjB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,WAAW;gBAC3C,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,MAAM,EAAE,EAAE,CAAC;gBACpD,GAAG,EAAE,IAAI;aACV,CAAC;SACH;QACD,WAAW,EAAE,CAAC,0CAAmB,CAAC;QAClC,SAAS,EAAE;YACT,oCAAgB;YAChB,6CAAoB;YACpB,iDAAsB;YACtB,+CAAqB;YACrB,6DAA4B;SAC7B;QACD,OAAO,EAAE,CAAC,oCAAgB,CAAC;KAC5B,CAAC;GACW,eAAe,CAAG"}
|
||||
+65
-6
@@ -1,17 +1,76 @@
|
||||
import { Repository } from 'typeorm';
|
||||
import type { Cache } from 'cache-manager';
|
||||
import { PlaceSyria } from './entities/place-syria.entity';
|
||||
import { PlaceJordan } from './entities/place-jordan.entity';
|
||||
import { PlaceEgypt } from './entities/place-egypt.entity';
|
||||
import { BasePlace } from './entities/base-place.entity';
|
||||
import { OsmArea } from './entities/osm-area.entity';
|
||||
import { OsmPointWithArea } from './entities/osm-point-with-area.entity';
|
||||
export declare class GeocodingService {
|
||||
private placesRepository;
|
||||
private placesSyriaRepository;
|
||||
private placesJordanRepository;
|
||||
private placesEgyptRepository;
|
||||
private osmAreasRepository;
|
||||
private osmPointsRepository;
|
||||
private cacheManager;
|
||||
private readonly logger;
|
||||
constructor(placesRepository: Repository<PlaceSyria>, osmAreasRepository: Repository<OsmArea>, osmPointsRepository: Repository<OsmPointWithArea>);
|
||||
searchPlaces(query: string): Promise<{
|
||||
userPlaces: PlaceSyria[];
|
||||
osmAreas: OsmArea[];
|
||||
private readonly DB_TIMEOUT_MS;
|
||||
constructor(placesSyriaRepository: Repository<PlaceSyria>, placesJordanRepository: Repository<PlaceJordan>, placesEgyptRepository: Repository<PlaceEgypt>, osmAreasRepository: Repository<OsmArea>, osmPointsRepository: Repository<OsmPointWithArea>, cacheManager: Cache);
|
||||
private getRepositoryForCoords;
|
||||
private identifyRegion;
|
||||
private getTableNameForRepo;
|
||||
searchPlaces(query: string, lat?: number, lon?: number, radius?: number, country?: string): Promise<{
|
||||
results: any;
|
||||
source: string;
|
||||
} | {
|
||||
results: any[];
|
||||
source?: undefined;
|
||||
}>;
|
||||
private formatResults;
|
||||
private getRepoByTableName;
|
||||
reverseGeocode(lat: number, lng: number): Promise<any>;
|
||||
addPlace(data: Partial<PlaceSyria>): Promise<PlaceSyria>;
|
||||
addPlace(data: Partial<BasePlace>): Promise<{
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
location: string;
|
||||
id: number;
|
||||
name: string;
|
||||
name_ar: string;
|
||||
name_en: string;
|
||||
address: string;
|
||||
category: string;
|
||||
neighbourhood: string;
|
||||
city: string;
|
||||
description: string;
|
||||
created_at: Date;
|
||||
source: string;
|
||||
governorate_id: number;
|
||||
district_id: number;
|
||||
sub_district_id: number;
|
||||
neighborhood_id: number;
|
||||
}>;
|
||||
upsertPlace(data: Partial<BasePlace>): Promise<{
|
||||
id: any;
|
||||
action: string;
|
||||
}>;
|
||||
upsertBatch(places: Partial<BasePlace>[]): Promise<{
|
||||
total: number;
|
||||
processed: number;
|
||||
created: number;
|
||||
updated: number;
|
||||
}>;
|
||||
getRecentPlaces(limit?: number): Promise<(PlaceSyria | PlaceJordan | PlaceEgypt)[]>;
|
||||
getAllPlacesGeoJSON(): Promise<{
|
||||
type: string;
|
||||
features: any;
|
||||
}>;
|
||||
deletePlacesByName(name: string, country: string): Promise<{
|
||||
success: boolean;
|
||||
affected: number;
|
||||
}>;
|
||||
deletePlaceById(id: number, country: string): Promise<{
|
||||
success: boolean;
|
||||
affected: number | null | undefined;
|
||||
}>;
|
||||
private getRepoByCountry;
|
||||
}
|
||||
|
||||
+257
-39
@@ -17,52 +17,188 @@ exports.GeocodingService = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const typeorm_1 = require("@nestjs/typeorm");
|
||||
const typeorm_2 = require("typeorm");
|
||||
const cache_manager_1 = require("@nestjs/cache-manager");
|
||||
const place_syria_entity_1 = require("./entities/place-syria.entity");
|
||||
const place_jordan_entity_1 = require("./entities/place-jordan.entity");
|
||||
const place_egypt_entity_1 = require("./entities/place-egypt.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;
|
||||
placesSyriaRepository;
|
||||
placesJordanRepository;
|
||||
placesEgyptRepository;
|
||||
osmAreasRepository;
|
||||
osmPointsRepository;
|
||||
cacheManager;
|
||||
logger = new common_1.Logger(GeocodingService_1.name);
|
||||
constructor(placesRepository, osmAreasRepository, osmPointsRepository) {
|
||||
this.placesRepository = placesRepository;
|
||||
DB_TIMEOUT_MS = 1100;
|
||||
constructor(placesSyriaRepository, placesJordanRepository, placesEgyptRepository, osmAreasRepository, osmPointsRepository, cacheManager) {
|
||||
this.placesSyriaRepository = placesSyriaRepository;
|
||||
this.placesJordanRepository = placesJordanRepository;
|
||||
this.placesEgyptRepository = placesEgyptRepository;
|
||||
this.osmAreasRepository = osmAreasRepository;
|
||||
this.osmPointsRepository = osmPointsRepository;
|
||||
this.cacheManager = cacheManager;
|
||||
}
|
||||
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,
|
||||
getRepositoryForCoords(lat, lng) {
|
||||
if (lat >= 29 && lat <= 37.5 && lng >= 34.5 && lng <= 42.5) {
|
||||
if (lat > 32.5 && lng > 35.8)
|
||||
return this.placesSyriaRepository;
|
||||
return this.placesJordanRepository;
|
||||
}
|
||||
if (lat >= 22 && lat <= 32 && lng >= 24.5 && lng <= 37) {
|
||||
return this.placesEgyptRepository;
|
||||
}
|
||||
return this.placesSyriaRepository;
|
||||
}
|
||||
identifyRegion(lat, lng) {
|
||||
if (lat === undefined || lng === undefined)
|
||||
return undefined;
|
||||
if (lat >= 29 && lat <= 37.5 && lng >= 34.5 && lng <= 42.5) {
|
||||
if (lat > 32.5 && lng > 35.8)
|
||||
return 'syria';
|
||||
return 'jordan';
|
||||
}
|
||||
if (lat >= 22 && lat <= 32 && lng >= 24.5 && lng <= 37) {
|
||||
return 'egypt';
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
getTableNameForRepo(repo) {
|
||||
if (repo === this.placesJordanRepository)
|
||||
return 'places_jordan';
|
||||
if (repo === this.placesEgyptRepository)
|
||||
return 'places_egypt';
|
||||
return 'places_syria';
|
||||
}
|
||||
async searchPlaces(query, lat, lon, radius = 20000, country) {
|
||||
try {
|
||||
const cleanQuery = query.trim();
|
||||
if (!cleanQuery)
|
||||
return { results: [] };
|
||||
const hasLocation = lat !== undefined && lon !== undefined;
|
||||
const geoSegment = hasLocation ? `${lat.toFixed(2)}_${lon.toFixed(2)}` : 'global';
|
||||
const cacheKey = `geo_search:${country || 'auto'}:${geoSegment}:${cleanQuery.toLowerCase()}`;
|
||||
const cached = await this.cacheManager.get(cacheKey);
|
||||
if (cached)
|
||||
return { results: cached, source: 'cache_hit' };
|
||||
let targetRegion = country?.toLowerCase() || this.identifyRegion(lat, lon);
|
||||
const primaryTables = targetRegion && ['syria', 'egypt', 'jordan'].includes(targetRegion)
|
||||
? [`places_${targetRegion}`] : ['places_jordan', 'places_syria', 'places_egypt'];
|
||||
const queryPromises = [];
|
||||
primaryTables.forEach(tableName => {
|
||||
const repo = this.getRepoByTableName(tableName);
|
||||
queryPromises.push(repo.query(`
|
||||
SELECT
|
||||
p.id, p.name, p.name_ar, p.name_en, p.category,
|
||||
n.name_ar as neighbourhood, d.name_ar as district, g.name_ar as governorate,
|
||||
p.latitude, p.longitude, p.address, '${tableName.replace('places_', '')}' as region, 'user_place' as source,
|
||||
CASE WHEN $2::float IS NOT NULL THEN ST_DistanceSphere(p.location, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) ELSE 0 END as distance,
|
||||
GREATEST(similarity(COALESCE(p.name_ar, ''), $1), similarity(COALESCE(p.name, ''), $1)) as relevance
|
||||
FROM ${tableName} p
|
||||
LEFT JOIN neighborhood_polygons n ON p.neighborhood_id = n.id
|
||||
LEFT JOIN admin_boundaries d ON p.district_id = d.id
|
||||
LEFT JOIN admin_boundaries g ON p.governorate_id = g.id
|
||||
WHERE (p.name_ar % $1 OR p.name % $1)
|
||||
AND ($2::float IS NULL OR ST_DistanceSphere(p.location, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) <= $4)
|
||||
ORDER BY (p.name_ar <-> $1) ASC LIMIT 10
|
||||
`, [cleanQuery, lat || null, lon || null, radius]));
|
||||
});
|
||||
queryPromises.push(this.osmPointsRepository.query(`
|
||||
SELECT
|
||||
o.osm_id as id, o.name, o.name_ar, o.name_en, COALESCE(o.amenity, o.shop, 'place') as category,
|
||||
o.latitude, o.longitude, o.addr_street as address, 'osm_global' as source,
|
||||
CASE WHEN $2::float IS NOT NULL THEN ST_DistanceSphere(o.geom, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) ELSE 0 END as distance,
|
||||
GREATEST(similarity(COALESCE(o.name, ''), $1), similarity(COALESCE(o.name_ar, ''), $1)) as relevance
|
||||
FROM osm_points_with_area o
|
||||
WHERE (o.name % $1 OR o.name_ar % $1)
|
||||
AND ($2::float IS NULL OR ST_DistanceSphere(o.geom, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) <= $4)
|
||||
ORDER BY (o.name <-> $1) ASC LIMIT 10
|
||||
`, [cleanQuery, lat || null, lon || null, radius]));
|
||||
const executionResults = await Promise.race([
|
||||
Promise.allSettled(queryPromises),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error('QUERY_TIMEOUT')), this.DB_TIMEOUT_MS))
|
||||
]).catch(e => {
|
||||
this.logger.warn(`Search optimization threshold hit: ${e.message}`);
|
||||
return [];
|
||||
});
|
||||
let allResults = [];
|
||||
if (Array.isArray(executionResults)) {
|
||||
executionResults.forEach(res => {
|
||||
if (res.status === 'fulfilled' && res.value)
|
||||
allResults.push(...res.value);
|
||||
});
|
||||
}
|
||||
const formatted = this.formatResults(allResults, hasLocation);
|
||||
if (formatted.length > 0) {
|
||||
await this.cacheManager.set(cacheKey, formatted, 3600000);
|
||||
}
|
||||
return { results: formatted };
|
||||
}
|
||||
catch (e) {
|
||||
this.logger.error('Search failed:', e);
|
||||
return { results: [] };
|
||||
}
|
||||
}
|
||||
formatResults(results, hasLocation) {
|
||||
const seenStreets = new Set();
|
||||
return results
|
||||
.map(r => {
|
||||
const proximityBonus = hasLocation ? Math.max(0, 1 - (Number(r.distance) / 10000)) : 0;
|
||||
const totalScore = (Number(r.relevance) * 0.7) + (proximityBonus * 0.3);
|
||||
return { ...r, totalScore };
|
||||
})
|
||||
.sort((a, b) => b.totalScore - a.totalScore)
|
||||
.filter(r => {
|
||||
if (r.category === 'street') {
|
||||
const key = `${r.name_ar || r.name}_${r.district}_${r.governorate}`;
|
||||
if (seenStreets.has(key))
|
||||
return false;
|
||||
seenStreets.add(key);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.slice(0, 4)
|
||||
.map(r => {
|
||||
const addressParts = [r.neighbourhood, r.district, r.governorate].filter(Boolean);
|
||||
const full_address = addressParts.length > 0 ? addressParts.join('، ') : (r.address || '');
|
||||
return {
|
||||
...r,
|
||||
latitude: parseFloat(r.latitude),
|
||||
longitude: parseFloat(r.longitude),
|
||||
distance_km: r.distance ? (Number(r.distance) / 1000).toFixed(2) : null,
|
||||
location: { lat: parseFloat(r.latitude), lng: parseFloat(r.longitude) },
|
||||
full_address,
|
||||
};
|
||||
});
|
||||
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,
|
||||
};
|
||||
}
|
||||
getRepoByTableName(tableName) {
|
||||
if (tableName === 'places_egypt')
|
||||
return this.placesEgyptRepository;
|
||||
if (tableName === 'places_jordan')
|
||||
return this.placesJordanRepository;
|
||||
return this.placesSyriaRepository;
|
||||
}
|
||||
async reverseGeocode(lat, lng) {
|
||||
try {
|
||||
const repo = this.getRepositoryForCoords(lat, lng);
|
||||
const tableName = this.getTableNameForRepo(repo);
|
||||
const query = `
|
||||
SELECT *, ST_Distance_Sphere(location, POINT(?, ?)) as distance
|
||||
FROM places_syria
|
||||
WHERE location IS NOT NULL
|
||||
ORDER BY distance ASC
|
||||
LIMIT 5
|
||||
SELECT
|
||||
p.id, p.name, p.name_ar, p.category,
|
||||
n.name_ar as neighbourhood,
|
||||
d.name_ar as district,
|
||||
g.name_ar as governorate,
|
||||
p.latitude, p.longitude, p.address, 'user_place' as source,
|
||||
ST_DistanceSphere(p.location::geometry, ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326)) as distance
|
||||
FROM ${tableName} p
|
||||
LEFT JOIN neighborhood_polygons n ON p.neighborhood_id = n.id
|
||||
LEFT JOIN admin_boundaries d ON p.sub_district_id = d.id
|
||||
LEFT JOIN admin_boundaries g ON p.governorate_id = g.id
|
||||
WHERE p.location IS NOT NULL
|
||||
ORDER BY p.location::geometry <-> ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) ASC LIMIT 3
|
||||
`;
|
||||
const results = await this.placesRepository.query(query, [lng, lat]);
|
||||
return results;
|
||||
return await repo.query(query, [lng, lat]);
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('Reverse geocoding error:', error);
|
||||
@@ -70,21 +206,103 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
|
||||
}
|
||||
}
|
||||
async addPlace(data) {
|
||||
const newPlace = this.placesRepository.create({
|
||||
...data,
|
||||
created_at: new Date(),
|
||||
});
|
||||
return this.placesRepository.save(newPlace);
|
||||
try {
|
||||
const lat = Number(data.latitude), lng = Number(data.longitude);
|
||||
const repo = this.getRepositoryForCoords(lat, lng), tableName = this.getTableNameForRepo(repo);
|
||||
const newPlace = repo.create({ ...data, latitude: lat, longitude: lng, created_at: new Date(), location: { type: 'Point', coordinates: [lng, lat] } });
|
||||
const savedPlace = await repo.save(newPlace);
|
||||
await repo.query(`UPDATE ${tableName} SET location = ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) WHERE id = $3`, [lng, lat, savedPlace.id]);
|
||||
return { ...savedPlace, latitude: lat, longitude: lng, location: `POINT(${lng} ${lat})` };
|
||||
}
|
||||
catch (error) {
|
||||
throw new common_1.HttpException(error.message, common_1.HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
async upsertPlace(data) {
|
||||
try {
|
||||
const lat = Number(data.latitude), lng = Number(data.longitude);
|
||||
const repo = this.getRepositoryForCoords(lat, lng), tableName = this.getTableNameForRepo(repo);
|
||||
const existing = await repo.query(`SELECT id, name FROM ${tableName} WHERE ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326)) < 15 LIMIT 1`, [lng, lat]);
|
||||
if (existing && existing.length > 0) {
|
||||
await repo.update(existing[0].id, { name: data.name || undefined, name_ar: data.name_ar || undefined, category: data.category || undefined });
|
||||
return { id: existing[0].id, action: 'updated' };
|
||||
}
|
||||
const result = await this.addPlace(data);
|
||||
return { id: result.id, action: 'created' };
|
||||
}
|
||||
catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async upsertBatch(places) {
|
||||
const results = [];
|
||||
for (const place of places) {
|
||||
try {
|
||||
const r = await this.addPlace(place);
|
||||
results.push({ id: r.id, action: 'created' });
|
||||
}
|
||||
catch (e) { }
|
||||
}
|
||||
return { total: places.length, processed: results.length, created: results.length, updated: 0 };
|
||||
}
|
||||
async getRecentPlaces(limit = 50) {
|
||||
const s = await this.placesSyriaRepository.find({ order: { created_at: 'DESC' }, take: limit });
|
||||
const j = await this.placesJordanRepository.find({ order: { created_at: 'DESC' }, take: limit });
|
||||
const e = await this.placesEgyptRepository.find({ order: { created_at: 'DESC' }, take: limit });
|
||||
return [...s, ...j, ...e].sort((a, b) => b.created_at.getTime() - a.created_at.getTime()).slice(0, limit);
|
||||
}
|
||||
async getAllPlacesGeoJSON() {
|
||||
try {
|
||||
const q = `
|
||||
SELECT id::text, name_ar as name, category, latitude, longitude, address, 'user' as region FROM places_syria
|
||||
UNION ALL SELECT id::text, name_ar as name, category, latitude, longitude, address, 'user' as region FROM places_jordan
|
||||
UNION ALL SELECT id::text, name_ar as name, category, latitude, longitude, address, 'user' as region FROM places_egypt
|
||||
UNION ALL SELECT id::text, COALESCE(names->>'primary', names->>'common', 'Building') as name, 'building' as category, ST_Y(ST_Centroid(location)) as latitude, ST_X(ST_Centroid(location)) as longitude, '' as address, 'overture' as region FROM overture_building WHERE names->>'primary' IS NOT NULL LIMIT 500
|
||||
UNION ALL SELECT id::text, COALESCE(names->>'primary', names->>'common', 'Street') as name, 'street' as category, ST_Y(ST_Centroid(location)) as latitude, ST_X(ST_Centroid(location)) as longitude, '' as address, 'overture' as region FROM overture_segment WHERE names->>'primary' IS NOT NULL LIMIT 500
|
||||
`;
|
||||
const res = await this.placesSyriaRepository.query(q);
|
||||
const features = res.map(p => ({
|
||||
type: 'Feature',
|
||||
geometry: { type: 'Point', coordinates: [parseFloat(p.longitude), parseFloat(p.latitude)] },
|
||||
properties: { id: p.id, name: p.name, category: p.category, address: p.address, region: p.region }
|
||||
}));
|
||||
return { type: 'FeatureCollection', features };
|
||||
}
|
||||
catch (e) {
|
||||
return { type: 'FeatureCollection', features: [] };
|
||||
}
|
||||
}
|
||||
async deletePlacesByName(name, country) {
|
||||
const repo = this.getRepoByCountry(country);
|
||||
const r1 = await repo.delete({ name_ar: name }), r2 = await repo.delete({ name_en: name }), r3 = await repo.delete({ name: name });
|
||||
return { success: true, affected: (r1.affected || 0) + (r2.affected || 0) + (r3.affected || 0) };
|
||||
}
|
||||
async deletePlaceById(id, country) {
|
||||
const repo = this.getRepoByCountry(country);
|
||||
return { success: true, affected: (await repo.delete(id)).affected };
|
||||
}
|
||||
getRepoByCountry(country) {
|
||||
switch (country?.toLowerCase()) {
|
||||
case 'syria': return this.placesSyriaRepository;
|
||||
case 'jordan': return this.placesJordanRepository;
|
||||
case 'egypt': return this.placesEgyptRepository;
|
||||
default: throw new common_1.HttpException('Invalid country: ' + country, common_1.HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
};
|
||||
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')),
|
||||
__param(0, (0, typeorm_1.InjectRepository)(place_syria_entity_1.PlaceSyria)),
|
||||
__param(1, (0, typeorm_1.InjectRepository)(place_jordan_entity_1.PlaceJordan)),
|
||||
__param(2, (0, typeorm_1.InjectRepository)(place_egypt_entity_1.PlaceEgypt)),
|
||||
__param(3, (0, typeorm_1.InjectRepository)(osm_area_entity_1.OsmArea)),
|
||||
__param(4, (0, typeorm_1.InjectRepository)(osm_point_with_area_entity_1.OsmPointWithArea)),
|
||||
__param(5, (0, common_1.Inject)(cache_manager_1.CACHE_MANAGER)),
|
||||
__metadata("design:paramtypes", [typeorm_2.Repository,
|
||||
typeorm_2.Repository,
|
||||
typeorm_2.Repository])
|
||||
typeorm_2.Repository,
|
||||
typeorm_2.Repository,
|
||||
typeorm_2.Repository, Object])
|
||||
], GeocodingService);
|
||||
//# sourceMappingURL=geocoding.service.js.map
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,25 @@
|
||||
import { Repository } from 'typeorm';
|
||||
import { PlaceJordan } from './entities/place-jordan.entity';
|
||||
export declare class JordanResearchService {
|
||||
private readonly placeRepo;
|
||||
private readonly logger;
|
||||
constructor(placeRepo: Repository<PlaceJordan>);
|
||||
private fetchWithRetry;
|
||||
fetchOsmZarqa(): Promise<any>;
|
||||
fetchOvertureZarqa(): Promise<any>;
|
||||
fetchWikidataZarqa(): Promise<any>;
|
||||
checkStaticFiles(): Promise<{}>;
|
||||
generateZarqaReport(): Promise<{
|
||||
area: string;
|
||||
report_timestamp: string;
|
||||
sources: {
|
||||
osm: any;
|
||||
overture: any;
|
||||
wikidata: any;
|
||||
static_files: {};
|
||||
hdx: string;
|
||||
gadm: string;
|
||||
};
|
||||
comparison_summary: string;
|
||||
}>;
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
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 __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
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 };
|
||||
};
|
||||
var JordanResearchService_1;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.JordanResearchService = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const typeorm_1 = require("@nestjs/typeorm");
|
||||
const typeorm_2 = require("typeorm");
|
||||
const place_jordan_entity_1 = require("./entities/place-jordan.entity");
|
||||
const axios_1 = __importDefault(require("axios"));
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
let JordanResearchService = JordanResearchService_1 = class JordanResearchService {
|
||||
placeRepo;
|
||||
logger = new common_1.Logger(JordanResearchService_1.name);
|
||||
constructor(placeRepo) {
|
||||
this.placeRepo = placeRepo;
|
||||
}
|
||||
async fetchWithRetry(url, data, method = 'get', retries = 2) {
|
||||
for (let i = 0; i <= retries; i++) {
|
||||
try {
|
||||
const config = {
|
||||
timeout: 20000,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; IntaleqMapBot/1.0; +https://intaleq.xyz)',
|
||||
'Accept': 'application/json, application/sparql-results+json'
|
||||
}
|
||||
};
|
||||
const response = method === 'post'
|
||||
? await axios_1.default.post(url, data, config)
|
||||
: await axios_1.default.get(url, { ...config, params: data ? { query: data, format: 'json' } : {} });
|
||||
return response.data;
|
||||
}
|
||||
catch (error) {
|
||||
if (i === retries) {
|
||||
this.logger.error(`Failed to fetch from ${url} after ${retries} retries: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
await new Promise(res => setTimeout(res, 2000));
|
||||
}
|
||||
}
|
||||
}
|
||||
async fetchOsmZarqa() {
|
||||
this.logger.log('Fetching Zarqa Geometries from OSM...');
|
||||
const query = `[out:json];(relation["boundary"="administrative"]["admin_level"~"6|8|10"](31.86,35.94,32.22,36.25);node["place"~"neighbourhood|suburb|town"](31.86,35.94,32.22,36.25););out geom;`;
|
||||
try {
|
||||
const data = await this.fetchWithRetry('https://overpass-api.de/api/interpreter', `data=${encodeURIComponent(query)}`, 'post');
|
||||
return data.elements.map(e => ({
|
||||
id: e.id,
|
||||
name_ar: e.tags['name:ar'] || e.tags['name'],
|
||||
type: e.tags['admin_level'] ? `admin_level_${e.tags['admin_level']}` : `place_${e.tags['place']}`,
|
||||
geometry: e.geometry ? e.geometry : (e.lat && e.lon ? { type: 'Point', coordinates: [e.lon, e.lat] } : null)
|
||||
}));
|
||||
}
|
||||
catch (e) {
|
||||
return { error: `OSM Fetch failed: ${e.message}` };
|
||||
}
|
||||
}
|
||||
async fetchOvertureZarqa() {
|
||||
try {
|
||||
const tableCheck = await this.placeRepo.query("SELECT count(*) FROM information_schema.tables WHERE table_name = 'overture_segment'");
|
||||
if (parseInt(tableCheck[0].count) === 0)
|
||||
return { error: 'table overture_segment does not exist in this database' };
|
||||
const columns = await this.placeRepo.query("SELECT column_name FROM information_schema.columns WHERE table_name = 'overture_segment'");
|
||||
const colList = columns.map(c => c.column_name);
|
||||
const roadClassCol = colList.includes('road_class') ? 'road_class' : (colList.includes('class') ? 'class' : 'NULL');
|
||||
return await this.placeRepo.query(`
|
||||
SELECT DISTINCT COALESCE(names->>'primary', names->>'common') as name_ar, ${roadClassCol} as road_class,
|
||||
ST_AsGeoJSON(ST_Centroid(location)) as centroid
|
||||
FROM overture_segment
|
||||
WHERE (names->>'primary' IS NOT NULL OR names->>'common' IS NOT NULL)
|
||||
AND ST_Within(location, ST_MakeEnvelope(35.94, 31.86, 36.25, 32.22, 4326))
|
||||
LIMIT 10
|
||||
`);
|
||||
}
|
||||
catch (e) {
|
||||
return { error: `Overture query failed: ${e.message}` };
|
||||
}
|
||||
}
|
||||
async fetchWikidataZarqa() {
|
||||
const sparql = `SELECT ?item ?itemLabel WHERE { ?item wdt:P131 wd:Q231710. SERVICE wikibase:label { bd:serviceParam wikibase:language "ar,en". } } LIMIT 20`;
|
||||
try {
|
||||
const data = await this.fetchWithRetry('https://query.wikidata.org/sparql', sparql, 'get');
|
||||
return data.results.bindings.map(b => ({ id: b.item.value.split('/').pop(), name_ar: b.itemLabel.value }));
|
||||
}
|
||||
catch (e) {
|
||||
return { error: `Wikidata Fetch failed: ${e.message}` };
|
||||
}
|
||||
}
|
||||
async checkStaticFiles() {
|
||||
const paths = ['/data/infrastructure/osm-data/', './data/', './infrastructure/osm-data/'];
|
||||
const files = ['jor_adm2_geoboundaries.geojson', 'gadm41_JOR_2.json'];
|
||||
const results = {};
|
||||
for (const file of files) {
|
||||
let found = false;
|
||||
for (const p of paths) {
|
||||
if (fs.existsSync(path.join(p, file))) {
|
||||
results[file] = `Found at ${p}`;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
results[file] = 'Not Found locally - Check if overture_ingest.sh was run for division_area';
|
||||
}
|
||||
return results;
|
||||
}
|
||||
async generateZarqaReport() {
|
||||
const [osm, overture, wikidata, staticFiles] = await Promise.all([
|
||||
this.fetchOsmZarqa(),
|
||||
this.fetchOvertureZarqa(),
|
||||
this.fetchWikidataZarqa(),
|
||||
this.checkStaticFiles()
|
||||
]);
|
||||
return {
|
||||
area: 'Zarqa Governorate, Jordan',
|
||||
report_timestamp: new Date().toISOString(),
|
||||
sources: {
|
||||
osm,
|
||||
overture,
|
||||
wikidata,
|
||||
static_files: staticFiles,
|
||||
hdx: "Requires GeoBoundaries GeoJSON for Level 2 (Districts)",
|
||||
gadm: "Requires GADM v4.1 for Level 1-2"
|
||||
},
|
||||
comparison_summary: "Multi-source research enabled with geometries. OSM provides precise neighborhood centroids and boundaries where available. Use the provided centroids to visualize Zarqa subunits."
|
||||
};
|
||||
}
|
||||
};
|
||||
exports.JordanResearchService = JordanResearchService;
|
||||
exports.JordanResearchService = JordanResearchService = JordanResearchService_1 = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__param(0, (0, typeorm_1.InjectRepository)(place_jordan_entity_1.PlaceJordan)),
|
||||
__metadata("design:paramtypes", [typeorm_2.Repository])
|
||||
], JordanResearchService);
|
||||
//# sourceMappingURL=jordan-research.service.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"jordan-research.service.js","sourceRoot":"","sources":["../../src/geocoding/jordan-research.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAAoD;AACpD,6CAAmD;AACnD,qCAAqC;AACrC,wEAA6D;AAC7D,kDAA0B;AAC1B,uCAAyB;AACzB,2CAA6B;AAGtB,IAAM,qBAAqB,6BAA3B,MAAM,qBAAqB;IAKb;IAJF,MAAM,GAAG,IAAI,eAAM,CAAC,uBAAqB,CAAC,IAAI,CAAC,CAAC;IAEjE,YAEmB,SAAkC;QAAlC,cAAS,GAAT,SAAS,CAAyB;IAClD,CAAC;IAEI,KAAK,CAAC,cAAc,CAAC,GAAW,EAAE,IAAa,EAAE,SAAyB,KAAK,EAAE,OAAO,GAAG,CAAC;QAClG,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5B,IAAI,CAAC;gBACD,MAAM,MAAM,GAAG;oBACX,OAAO,EAAE,KAAK;oBACd,OAAO,EAAE;wBACL,YAAY,EAAE,mEAAmE;wBACjF,QAAQ,EAAE,mDAAmD;qBAChE;iBACJ,CAAC;gBACF,MAAM,QAAQ,GAAG,MAAM,KAAK,MAAM;oBAC9B,CAAC,CAAC,MAAM,eAAK,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC;oBACrC,CAAC,CAAC,MAAM,eAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC/F,OAAO,QAAQ,CAAC,IAAI,CAAC;YACzB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,IAAI,CAAC,KAAK,OAAO,EAAE,CAAC;oBAChB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,wBAAwB,GAAG,UAAU,OAAO,aAAa,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;oBAC5F,MAAM,KAAK,CAAC;gBAChB,CAAC;gBACD,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;YACpD,CAAC;QACL,CAAC;IACP,CAAC;IAKD,KAAK,CAAC,aAAa;QACjB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;QACzD,MAAM,KAAK,GAAG,mLAAmL,CAAC;QAClM,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,yCAAyC,EAAE,QAAQ,kBAAkB,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;YAC/H,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBAC7B,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;gBAC5C,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;gBACjG,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;aAC7G,CAAC,CAAC,CAAC;QACN,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YAAC,OAAO,EAAE,KAAK,EAAE,qBAAqB,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QAAC,CAAC;IACrE,CAAC;IAKD,KAAK,CAAC,kBAAkB;QACtB,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,sFAAsF,CAAC,CAAC;YACtI,IAAI,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;gBAAE,OAAO,EAAE,KAAK,EAAE,wDAAwD,EAAE,CAAC;YAGpH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,0FAA0F,CAAC,CAAC;YACvI,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;YAChD,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YAEpH,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;oFAC4C,YAAY;;;;;;OAMzF,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YAAC,OAAO,EAAE,KAAK,EAAE,0BAA0B,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QAAC,CAAC;IAC1E,CAAC;IAKD,KAAK,CAAC,kBAAkB;QACtB,MAAM,MAAM,GAAG,6IAA6I,CAAC;QAC7J,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,mCAAmC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;YAC3F,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAC7G,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YAAC,OAAO,EAAE,KAAK,EAAE,0BAA0B,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QAAC,CAAC;IAC1E,CAAC;IAKD,KAAK,CAAC,gBAAgB;QAEpB,MAAM,KAAK,GAAG,CAAC,gCAAgC,EAAE,SAAS,EAAE,4BAA4B,CAAC,CAAC;QAC1F,MAAM,KAAK,GAAG,CAAC,gCAAgC,EAAE,mBAAmB,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,EAAE,CAAC;QAEnB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,IAAI,KAAK,GAAG,KAAK,CAAC;YAClB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;gBACpB,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;oBACpC,OAAO,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,EAAE,CAAC;oBAChC,KAAK,GAAG,IAAI,CAAC;oBACb,MAAM;gBACV,CAAC;YACL,CAAC;YACD,IAAI,CAAC,KAAK;gBAAE,OAAO,CAAC,IAAI,CAAC,GAAG,2EAA2E,CAAC;QAC5G,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,mBAAmB;QACvB,MAAM,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAC/D,IAAI,CAAC,aAAa,EAAE;YACpB,IAAI,CAAC,kBAAkB,EAAE;YACzB,IAAI,CAAC,kBAAkB,EAAE;YACzB,IAAI,CAAC,gBAAgB,EAAE;SACxB,CAAC,CAAC;QAEH,OAAO;YACL,IAAI,EAAE,2BAA2B;YACjC,gBAAgB,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC1C,OAAO,EAAE;gBACP,GAAG;gBACH,QAAQ;gBACR,QAAQ;gBACR,YAAY,EAAE,WAAW;gBACzB,GAAG,EAAE,wDAAwD;gBAC7D,IAAI,EAAE,kCAAkC;aACzC;YACD,kBAAkB,EAAE,oLAAoL;SACzM,CAAC;IACJ,CAAC;CACF,CAAA;AAjIY,sDAAqB;gCAArB,qBAAqB;IADjC,IAAA,mBAAU,GAAE;IAKR,WAAA,IAAA,0BAAgB,EAAC,iCAAW,CAAC,CAAA;qCACF,oBAAU;GAL7B,qBAAqB,CAiIjC"}
|
||||
Vendored
+13
@@ -1,17 +1,30 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const core_1 = require("@nestjs/core");
|
||||
const common_1 = require("@nestjs/common");
|
||||
const swagger_1 = require("@nestjs/swagger");
|
||||
const helmet_1 = __importDefault(require("helmet"));
|
||||
const app_module_1 = require("./app.module");
|
||||
async function bootstrap() {
|
||||
const app = await core_1.NestFactory.create(app_module_1.AppModule);
|
||||
app.use((0, helmet_1.default)());
|
||||
app.enableCors();
|
||||
app.setGlobalPrefix('api');
|
||||
app.useGlobalPipes(new common_1.ValidationPipe({
|
||||
whitelist: true,
|
||||
forbidNonWhitelisted: true,
|
||||
transform: true,
|
||||
}));
|
||||
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')
|
||||
.addApiKey({ type: 'apiKey', name: 'x-api-key', in: 'header' }, 'x-api-key')
|
||||
.addTag('maps')
|
||||
.addTag('geocoding')
|
||||
.addTag('telemetry')
|
||||
.build();
|
||||
const document = swagger_1.SwaggerModule.createDocument(app, config);
|
||||
|
||||
Vendored
+1
-1
@@ -1 +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"}
|
||||
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";;;;;AAAA,uCAAsD;AACtD,2CAAgD;AAChD,6CAAiE;AACjE,oDAA4B;AAC5B,6CAAyC;AAEzC,KAAK,UAAU,SAAS;IACtB,MAAM,GAAG,GAAG,MAAM,kBAAW,CAAC,MAAM,CAAC,sBAAS,CAAC,CAAC;IAGhD,GAAG,CAAC,GAAG,CAAC,IAAA,gBAAM,GAAE,CAAC,CAAC;IAGlB,GAAG,CAAC,UAAU,EAAE,CAAC;IAGjB,GAAG,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAG3B,GAAG,CAAC,cAAc,CAAC,IAAI,uBAAc,CAAC;QACpC,SAAS,EAAE,IAAI;QACf,oBAAoB,EAAE,IAAI;QAC1B,SAAS,EAAE,IAAI;KAChB,CAAC,CAAC,CAAC;IAGJ,MAAM,MAAM,GAAG,IAAI,yBAAe,EAAE;SACjC,QAAQ,CAAC,yBAAyB,CAAC;SACnC,cAAc,CAAC,wEAAwE,CAAC;SACxF,UAAU,CAAC,KAAK,CAAC;SACjB,SAAS,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,WAAW,CAAC;SAC3E,MAAM,CAAC,MAAM,CAAC;SACd,MAAM,CAAC,WAAW,CAAC;SACnB,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"}
|
||||
+6
-2
@@ -2,12 +2,16 @@ 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<{
|
||||
getRoute(query: any): Promise<{
|
||||
distance: any;
|
||||
duration: number;
|
||||
trafficAwareDuration: number;
|
||||
trafficFactor: number;
|
||||
startName: string;
|
||||
endName: string;
|
||||
points: any;
|
||||
bbox: any;
|
||||
congestionAware: boolean;
|
||||
alternatives: any;
|
||||
}>;
|
||||
getConfig(): Promise<{
|
||||
center: number[];
|
||||
|
||||
+27
-8
@@ -22,8 +22,30 @@ let MapsController = class MapsController {
|
||||
constructor(mapsService) {
|
||||
this.mapsService = mapsService;
|
||||
}
|
||||
async getRoute(fromLat, fromLng, toLat, toLng) {
|
||||
return this.mapsService.getRoute([fromLat, fromLng], [toLat, toLng]);
|
||||
async getRoute(query) {
|
||||
const waypoints = [];
|
||||
if (query.fromLat && query.fromLng) {
|
||||
waypoints.push([parseFloat(query.fromLat), parseFloat(query.fromLng)]);
|
||||
}
|
||||
const stopKeys = Object.keys(query)
|
||||
.filter(key => key.startsWith('stop') && key.endsWith('Lat'))
|
||||
.sort((a, b) => {
|
||||
const numA = parseInt(a.replace('stop', '').replace('Lat', ''), 10);
|
||||
const numB = parseInt(b.replace('stop', '').replace('Lat', ''), 10);
|
||||
return numA - numB;
|
||||
});
|
||||
for (const latKey of stopKeys) {
|
||||
const prefix = latKey.replace('Lat', '');
|
||||
const lngKey = `${prefix}Lng`;
|
||||
if (query[latKey] && query[lngKey]) {
|
||||
waypoints.push([parseFloat(query[latKey]), parseFloat(query[lngKey])]);
|
||||
}
|
||||
}
|
||||
if (query.toLat && query.toLng) {
|
||||
waypoints.push([parseFloat(query.toLat), parseFloat(query.toLng)]);
|
||||
}
|
||||
const profile = query.profile || 'car';
|
||||
return this.mapsService.getRoute(waypoints, profile);
|
||||
}
|
||||
async getConfig() {
|
||||
return this.mapsService.getMapConfig();
|
||||
@@ -32,13 +54,10 @@ let MapsController = class MapsController {
|
||||
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')),
|
||||
(0, swagger_1.ApiOperation)({ summary: 'Calculate a route with dynamic waypoints 🚗' }),
|
||||
__param(0, (0, common_1.Query)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Number, Number, Number, Number]),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], MapsController.prototype, "getRoute", null);
|
||||
__decorate([
|
||||
|
||||
+1
-1
@@ -1 +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"}
|
||||
{"version":3,"file":"maps.controller.js","sourceRoot":"","sources":["../../src/maps/maps.controller.ts"],"names":[],"mappings":"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"}
|
||||
Vendored
+5
-2
@@ -10,10 +10,12 @@ exports.MapsModule = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const typeorm_1 = require("@nestjs/typeorm");
|
||||
const maps_service_1 = require("./maps.service");
|
||||
const traffic_grid_service_1 = require("./traffic-grid.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");
|
||||
const geocoding_module_1 = require("../geocoding/geocoding.module");
|
||||
let MapsModule = class MapsModule {
|
||||
};
|
||||
exports.MapsModule = MapsModule;
|
||||
@@ -22,10 +24,11 @@ exports.MapsModule = MapsModule = __decorate([
|
||||
imports: [
|
||||
typeorm_1.TypeOrmModule.forFeature([road_stat_entity_1.RoadSegmentStat, candidate_road_entity_1.CandidateRoad]),
|
||||
redis_module_1.RedisModule,
|
||||
geocoding_module_1.GeocodingModule,
|
||||
],
|
||||
controllers: [maps_controller_1.MapsController],
|
||||
providers: [maps_service_1.MapsService],
|
||||
exports: [maps_service_1.MapsService],
|
||||
providers: [maps_service_1.MapsService, traffic_grid_service_1.TrafficGridService],
|
||||
exports: [maps_service_1.MapsService, traffic_grid_service_1.TrafficGridService],
|
||||
})
|
||||
], MapsModule);
|
||||
//# sourceMappingURL=maps.module.js.map
|
||||
+1
-1
@@ -1 +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"}
|
||||
{"version":3,"file":"maps.module.js","sourceRoot":"","sources":["../../src/maps/maps.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,6CAAgD;AAChD,iDAA6C;AAC7C,iEAA4D;AAC5D,uDAAmD;AACnD,yDAAqD;AACrD,mEAAwD;AACxD,yDAAqD;AACrD,oEAAgE;AAYzD,IAAM,UAAU,GAAhB,MAAM,UAAU;CAAG,CAAA;AAAb,gCAAU;qBAAV,UAAU;IAVtB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,uBAAa,CAAC,UAAU,CAAC,CAAC,kCAAe,EAAE,qCAAa,CAAC,CAAC;YAC1D,0BAAW;YACX,kCAAe;SAChB;QACD,WAAW,EAAE,CAAC,gCAAc,CAAC;QAC7B,SAAS,EAAE,CAAC,0BAAW,EAAE,yCAAkB,CAAC;QAC5C,OAAO,EAAE,CAAC,0BAAW,EAAE,yCAAkB,CAAC;KAC3C,CAAC;GACW,UAAU,CAAG"}
|
||||
Vendored
+13
-8
@@ -1,22 +1,27 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Repository, DataSource } from 'typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { RoadSegmentStat } from './road-stat.entity';
|
||||
import { RedisService } from '../common/redis.service';
|
||||
import { TrafficGridService } from './traffic-grid.service';
|
||||
import { GeocodingService } from '../geocoding/geocoding.service';
|
||||
export declare class MapsService {
|
||||
private configService;
|
||||
private roadStatRepo;
|
||||
private dataSource;
|
||||
private redisService;
|
||||
private trafficGrid;
|
||||
private geocodingService;
|
||||
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<{
|
||||
constructor(configService: ConfigService, roadStatRepo: Repository<RoadSegmentStat>, trafficGrid: TrafficGridService, geocodingService: GeocodingService);
|
||||
getRoute(waypoints: [number, number][], profile?: string): Promise<{
|
||||
distance: any;
|
||||
duration: number;
|
||||
trafficAwareDuration: number;
|
||||
trafficFactor: number;
|
||||
startName: string;
|
||||
endName: string;
|
||||
points: any;
|
||||
bbox: any;
|
||||
congestionAware: boolean;
|
||||
alternatives: any;
|
||||
}>;
|
||||
private decodePolyline;
|
||||
getMapConfig(): Promise<{
|
||||
center: number[];
|
||||
zoom: number;
|
||||
|
||||
Vendored
+101
-72
@@ -20,100 +20,129 @@ 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"));
|
||||
const road_stat_entity_1 = require("./road-stat.entity");
|
||||
const traffic_grid_service_1 = require("./traffic-grid.service");
|
||||
const geocoding_service_1 = require("../geocoding/geocoding.service");
|
||||
let MapsService = class MapsService {
|
||||
configService;
|
||||
roadStatRepo;
|
||||
dataSource;
|
||||
redisService;
|
||||
trafficGrid;
|
||||
geocodingService;
|
||||
graphHopperUrl;
|
||||
TRAFFIC_CACHE_KEY = 'traffic_snapshot';
|
||||
constructor(configService, roadStatRepo, dataSource, redisService) {
|
||||
constructor(configService, roadStatRepo, trafficGrid, geocodingService) {
|
||||
this.configService = configService;
|
||||
this.roadStatRepo = roadStatRepo;
|
||||
this.dataSource = dataSource;
|
||||
this.redisService = redisService;
|
||||
this.graphHopperUrl = this.configService.get('GRAPH_HOPPER_URL', 'http://map-routing:8080');
|
||||
this.trafficGrid = trafficGrid;
|
||||
this.geocodingService = geocodingService;
|
||||
this.graphHopperUrl = this.configService.get('GRAPH_HOPPER_URL', 'http://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)
|
||||
});
|
||||
});
|
||||
async getRoute(waypoints, profile = 'car') {
|
||||
if (waypoints.length < 2) {
|
||||
throw new common_1.HttpException('At least two waypoints are required', common_1.HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
try {
|
||||
const params = {
|
||||
point: [`${from[0]},${from[1]}`, `${to[0]},${to[1]}`],
|
||||
profile: 'car',
|
||||
const ghPoints = waypoints.map(wp => [wp[1], wp[0]]);
|
||||
let startName = 'Unknown Location';
|
||||
let endName = 'Unknown Location';
|
||||
try {
|
||||
const startWp = waypoints[0];
|
||||
const endWp = waypoints[waypoints.length - 1];
|
||||
const [startRes, endRes] = await Promise.all([
|
||||
this.geocodingService.reverseGeocode(startWp[0], startWp[1]),
|
||||
this.geocodingService.reverseGeocode(endWp[0], endWp[1])
|
||||
]);
|
||||
const formatName = (r) => {
|
||||
const parts = [r.name_ar || r.name, r.neighbourhood, r.district, r.governorate].filter(Boolean);
|
||||
const uniqueParts = [...new Set(parts)];
|
||||
return uniqueParts.length > 0 ? uniqueParts.join('، ') : 'Unknown Location';
|
||||
};
|
||||
if (startRes && startRes.length > 0)
|
||||
startName = formatName(startRes[0]);
|
||||
if (endRes && endRes.length > 0)
|
||||
endName = formatName(endRes[0]);
|
||||
}
|
||||
catch (e) {
|
||||
console.warn('Geocoding internal error during routing:', e);
|
||||
}
|
||||
const payload = {
|
||||
points: ghPoints,
|
||||
profile: profile,
|
||||
locale: 'en',
|
||||
calc_points: true,
|
||||
points_encoded: true,
|
||||
elevation: false,
|
||||
type: 'json',
|
||||
};
|
||||
if (customModel.speed.length > 0) {
|
||||
params.custom_model = JSON.stringify(customModel);
|
||||
if (waypoints.length === 2) {
|
||||
payload.algorithm = 'alternative_route';
|
||||
payload['ch.disable'] = true;
|
||||
payload['alternative_route.max_paths'] = 2;
|
||||
payload['alternative_route.max_weight_factor'] = 1.6;
|
||||
payload['alternative_route.max_share_factor'] = 0.6;
|
||||
}
|
||||
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) {
|
||||
console.log(`Routing Request: ${waypoints.length} points via ${profile} on ${this.graphHopperUrl}`);
|
||||
const response = await axios_1.default.post(`${this.graphHopperUrl}/route`, payload, { timeout: 10000 });
|
||||
console.log('Routing SUCCESS');
|
||||
const paths = response.data.paths;
|
||||
if (!paths || paths.length === 0)
|
||||
throw new common_1.HttpException('No route found', common_1.HttpStatus.NOT_FOUND);
|
||||
}
|
||||
const route = paths[0];
|
||||
const now = new Date();
|
||||
const hr = now.getHours();
|
||||
const dow = now.getDay();
|
||||
const coords = this.decodePolyline(route.points);
|
||||
const trafficFactor = this.trafficGrid.getTrafficFactor(coords, hr, dow);
|
||||
const baseDuration = route.time / 1000;
|
||||
const trafficAwareDuration = baseDuration * trafficFactor;
|
||||
const alternatives = paths.slice(1).map(alt => ({
|
||||
distance: alt.distance,
|
||||
duration: Math.round(alt.time / 1000),
|
||||
points: alt.points,
|
||||
bbox: alt.bbox
|
||||
}));
|
||||
return {
|
||||
distance: route.distance,
|
||||
duration: route.time / 1000,
|
||||
duration: Math.round(baseDuration),
|
||||
trafficAwareDuration: Math.round(trafficAwareDuration),
|
||||
trafficFactor: Math.round(trafficFactor * 100) / 100,
|
||||
startName,
|
||||
endName,
|
||||
points: route.points,
|
||||
bbox: route.bbox,
|
||||
congestionAware: customModel.speed.length > 0,
|
||||
alternatives: alternatives
|
||||
};
|
||||
}
|
||||
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);
|
||||
const msg = error.response ? `GH Error: ${JSON.stringify(error.response.data)}` : `DNS/Connection Error: ${error.message}`;
|
||||
console.error('CRITICAL ROUTING FAILURE:', msg);
|
||||
throw new common_1.HttpException(`Routing Failure: ${msg}`, common_1.HttpStatus.BAD_GATEWAY);
|
||||
}
|
||||
}
|
||||
decodePolyline(encoded) {
|
||||
const points = [];
|
||||
let index = 0, len = encoded.length;
|
||||
let lat = 0, lng = 0;
|
||||
while (index < len) {
|
||||
let b, shift = 0, result = 0;
|
||||
do {
|
||||
b = encoded.charCodeAt(index++) - 63;
|
||||
result |= (b & 0x1f) << shift;
|
||||
shift += 5;
|
||||
} while (b >= 0x20);
|
||||
let dlat = ((result & 1) ? ~(result >> 1) : (result >> 1));
|
||||
lat += dlat;
|
||||
shift = 0;
|
||||
result = 0;
|
||||
do {
|
||||
b = encoded.charCodeAt(index++) - 63;
|
||||
result |= (b & 0x1f) << shift;
|
||||
shift += 5;
|
||||
} while (b >= 0x20);
|
||||
let dlng = ((result & 1) ? ~(result >> 1) : (result >> 1));
|
||||
lng += dlng;
|
||||
points.push([lng * 1e-5, lat * 1e-5]);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
async getMapConfig() {
|
||||
return {
|
||||
center: [31.95, 35.91],
|
||||
@@ -128,7 +157,7 @@ exports.MapsService = MapsService = __decorate([
|
||||
__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])
|
||||
traffic_grid_service_1.TrafficGridService,
|
||||
geocoding_service_1.GeocodingService])
|
||||
], MapsService);
|
||||
//# sourceMappingURL=maps.service.js.map
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
|
||||
export declare class RoadSpeedProfile {
|
||||
segmentId: string;
|
||||
hourOfDay: number;
|
||||
dayOfWeek: number;
|
||||
averageSpeed: number;
|
||||
sampleCount: number;
|
||||
lastUpdated: Date;
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
"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.RoadSpeedProfile = void 0;
|
||||
const typeorm_1 = require("typeorm");
|
||||
let RoadSpeedProfile = class RoadSpeedProfile {
|
||||
segmentId;
|
||||
hourOfDay;
|
||||
dayOfWeek;
|
||||
averageSpeed;
|
||||
sampleCount;
|
||||
lastUpdated;
|
||||
};
|
||||
exports.RoadSpeedProfile = RoadSpeedProfile;
|
||||
__decorate([
|
||||
(0, typeorm_1.PrimaryColumn)(),
|
||||
__metadata("design:type", String)
|
||||
], RoadSpeedProfile.prototype, "segmentId", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.PrimaryColumn)(),
|
||||
__metadata("design:type", Number)
|
||||
], RoadSpeedProfile.prototype, "hourOfDay", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.PrimaryColumn)(),
|
||||
__metadata("design:type", Number)
|
||||
], RoadSpeedProfile.prototype, "dayOfWeek", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)('float', { default: 0 }),
|
||||
__metadata("design:type", Number)
|
||||
], RoadSpeedProfile.prototype, "averageSpeed", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)('int', { default: 0 }),
|
||||
__metadata("design:type", Number)
|
||||
], RoadSpeedProfile.prototype, "sampleCount", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.CreateDateColumn)(),
|
||||
__metadata("design:type", Date)
|
||||
], RoadSpeedProfile.prototype, "lastUpdated", void 0);
|
||||
exports.RoadSpeedProfile = RoadSpeedProfile = __decorate([
|
||||
(0, typeorm_1.Entity)('road_speed_profiles'),
|
||||
(0, typeorm_1.Index)(['segmentId', 'hourOfDay', 'dayOfWeek'], { unique: true })
|
||||
], RoadSpeedProfile);
|
||||
//# sourceMappingURL=road-speed-profile.entity.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"road-speed-profile.entity.js","sourceRoot":"","sources":["../../src/maps/road-speed-profile.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAAiF;AAI1E,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IAE3B,SAAS,CAAS;IAGlB,SAAS,CAAS;IAGlB,SAAS,CAAS;IAGlB,YAAY,CAAS;IAGrB,WAAW,CAAS;IAGpB,WAAW,CAAO;CACnB,CAAA;AAlBY,4CAAgB;AAE3B;IADC,IAAA,uBAAa,GAAE;;mDACE;AAGlB;IADC,IAAA,uBAAa,GAAE;;mDACE;AAGlB;IADC,IAAA,uBAAa,GAAE;;mDACE;AAGlB;IADC,IAAA,gBAAM,EAAC,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;sDACX;AAGrB;IADC,IAAA,gBAAM,EAAC,KAAK,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;qDACV;AAGpB;IADC,IAAA,0BAAgB,GAAE;8BACN,IAAI;qDAAC;2BAjBP,gBAAgB;IAF5B,IAAA,gBAAM,EAAC,qBAAqB,CAAC;IAC7B,IAAA,eAAK,EAAC,CAAC,WAAW,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;GACpD,gBAAgB,CAkB5B"}
|
||||
+1
@@ -2,6 +2,7 @@ export declare class RoadSegmentStat {
|
||||
segmentId: string;
|
||||
averageSpeed: number;
|
||||
congestionFactor: number;
|
||||
isClosed: boolean;
|
||||
sampleCount: number;
|
||||
geometry: any;
|
||||
lastUpdated: Date;
|
||||
|
||||
+8
-1
@@ -15,6 +15,7 @@ let RoadSegmentStat = class RoadSegmentStat {
|
||||
segmentId;
|
||||
averageSpeed;
|
||||
congestionFactor;
|
||||
isClosed;
|
||||
sampleCount;
|
||||
geometry;
|
||||
lastUpdated;
|
||||
@@ -32,6 +33,11 @@ __decorate([
|
||||
(0, typeorm_1.Column)('float', { default: 1.0 }),
|
||||
__metadata("design:type", Number)
|
||||
], RoadSegmentStat.prototype, "congestionFactor", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)('boolean', { default: false }),
|
||||
(0, typeorm_1.Index)(),
|
||||
__metadata("design:type", Boolean)
|
||||
], RoadSegmentStat.prototype, "isClosed", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)('int', { default: 0 }),
|
||||
__metadata("design:type", Number)
|
||||
@@ -51,6 +57,7 @@ __decorate([
|
||||
__metadata("design:type", Date)
|
||||
], RoadSegmentStat.prototype, "lastUpdated", void 0);
|
||||
exports.RoadSegmentStat = RoadSegmentStat = __decorate([
|
||||
(0, typeorm_1.Entity)('road_segment_stats')
|
||||
(0, typeorm_1.Entity)('road_segment_stats'),
|
||||
(0, typeorm_1.Index)('idx_segment_congestion', ['congestionFactor'], { where: '"congestionFactor" > 1.1' })
|
||||
], RoadSegmentStat);
|
||||
//# sourceMappingURL=road-stat.entity.js.map
|
||||
+1
-1
@@ -1 +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"}
|
||||
{"version":3,"file":"road-stat.entity.js","sourceRoot":"","sources":["../../src/maps/road-stat.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAA+D;AAIxD,IAAM,eAAe,GAArB,MAAM,eAAe;IAE1B,SAAS,CAAS;IAGlB,YAAY,CAAS;IAGrB,gBAAgB,CAAS;IAIzB,QAAQ,CAAU;IAGlB,WAAW,CAAS;IASpB,QAAQ,CAAM;IAGd,WAAW,CAAO;CACnB,CAAA;AA5BY,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;AAIzB;IAFC,IAAA,gBAAM,EAAC,SAAS,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IACrC,IAAA,eAAK,GAAE;;iDACU;AAGlB;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;0BA3BP,eAAe;IAF3B,IAAA,gBAAM,EAAC,oBAAoB,CAAC;IAC5B,IAAA,eAAK,EAAC,wBAAwB,EAAE,CAAC,kBAAkB,CAAC,EAAE,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC;GAChF,eAAe,CA4B3B"}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { OnModuleInit } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
export declare class TrafficGridService implements OnModuleInit {
|
||||
private dataSource;
|
||||
private readonly logger;
|
||||
private grid;
|
||||
private readonly GEOHASH_PRECISION;
|
||||
constructor(dataSource: DataSource);
|
||||
onModuleInit(): Promise<void>;
|
||||
refreshGrid(): Promise<void>;
|
||||
getTrafficFactor(coords: [number, number][], hr: number, dow: number): number;
|
||||
private encodeGeohash;
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
"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 TrafficGridService_1;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.TrafficGridService = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const typeorm_1 = require("typeorm");
|
||||
let TrafficGridService = TrafficGridService_1 = class TrafficGridService {
|
||||
dataSource;
|
||||
logger = new common_1.Logger(TrafficGridService_1.name);
|
||||
grid = new Map();
|
||||
GEOHASH_PRECISION = 6;
|
||||
constructor(dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
async onModuleInit() {
|
||||
await this.refreshGrid();
|
||||
}
|
||||
async refreshGrid() {
|
||||
this.logger.log('📡 Rebuilding Traffic Grid v3 (In-Memory Lookup)...');
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
const data = await this.dataSource.query(`
|
||||
SELECT
|
||||
ST_X(ST_Centroid(geometry::geometry)) as lng,
|
||||
ST_Y(ST_Centroid(geometry::geometry)) as lat,
|
||||
p."hourOfDay", p."dayOfWeek", p."averageSpeed",
|
||||
s."averageSpeed" as base_speed
|
||||
FROM road_speed_profiles p
|
||||
JOIN road_segment_stats s ON p."segmentId" = s."segmentId"
|
||||
WHERE s.geometry IS NOT NULL
|
||||
`);
|
||||
this.grid.clear();
|
||||
let count = 0;
|
||||
for (const row of data) {
|
||||
const gh = this.encodeGeohash(row.lat, row.lng, this.GEOHASH_PRECISION);
|
||||
const factor = row.base_speed / Math.max(row.averageSpeed, 1);
|
||||
const key = `${gh}:${row.hourOfDay}:${row.dayOfWeek}`;
|
||||
this.grid.set(key, factor);
|
||||
count++;
|
||||
}
|
||||
this.logger.log(`✅ Grid rebuilt in ${Date.now() - startTime}ms. Cached ${count} cells (${this.grid.size} unique keys).`);
|
||||
}
|
||||
catch (e) {
|
||||
this.logger.error('❌ Failed to refresh traffic grid:', e.message);
|
||||
}
|
||||
}
|
||||
getTrafficFactor(coords, hr, dow) {
|
||||
if (this.grid.size === 0)
|
||||
return 1.0;
|
||||
let totalFactor = 0;
|
||||
let matches = 0;
|
||||
for (let i = 0; i < coords.length; i += 2) {
|
||||
const [lng, lat] = coords[i];
|
||||
const gh = this.encodeGeohash(lat, lng, this.GEOHASH_PRECISION);
|
||||
const key = `${gh}:${hr}:${dow}`;
|
||||
const factor = this.grid.get(key);
|
||||
if (factor) {
|
||||
totalFactor += factor;
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
if (matches === 0)
|
||||
return 1.0;
|
||||
let finalFactor = totalFactor / matches;
|
||||
return Math.min(Math.max(finalFactor, 0.8), 3.0);
|
||||
}
|
||||
encodeGeohash(lat, lng, precision) {
|
||||
const BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz";
|
||||
let minLat = -90, maxLat = 90;
|
||||
let minLng = -180, maxLng = 180;
|
||||
let result = "";
|
||||
let isEven = true;
|
||||
let bit = 0;
|
||||
let ch = 0;
|
||||
while (result.length < precision) {
|
||||
if (isEven) {
|
||||
let mid = (minLng + maxLng) / 2;
|
||||
if (lng > mid) {
|
||||
ch |= (1 << (4 - bit));
|
||||
minLng = mid;
|
||||
}
|
||||
else {
|
||||
maxLng = mid;
|
||||
}
|
||||
}
|
||||
else {
|
||||
let mid = (minLat + maxLat) / 2;
|
||||
if (lat > mid) {
|
||||
ch |= (1 << (4 - bit));
|
||||
minLat = mid;
|
||||
}
|
||||
else {
|
||||
maxLat = mid;
|
||||
}
|
||||
}
|
||||
isEven = !isEven;
|
||||
if (bit < 4) {
|
||||
bit++;
|
||||
}
|
||||
else {
|
||||
result += BASE32[ch];
|
||||
bit = 0;
|
||||
ch = 0;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
exports.TrafficGridService = TrafficGridService;
|
||||
exports.TrafficGridService = TrafficGridService = TrafficGridService_1 = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__metadata("design:paramtypes", [typeorm_1.DataSource])
|
||||
], TrafficGridService);
|
||||
//# sourceMappingURL=traffic-grid.service.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"traffic-grid.service.js","sourceRoot":"","sources":["../../src/maps/traffic-grid.service.ts"],"names":[],"mappings":"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"}
|
||||
+20
-25
@@ -4,42 +4,37 @@ export declare class MapRefinementController {
|
||||
constructor(analyzerService: TelemetryAnalyzerService);
|
||||
getSummary(): Promise<{
|
||||
telemetry: {
|
||||
totalPoints: number;
|
||||
last24h: number;
|
||||
activeDrivers24h: number;
|
||||
total: any;
|
||||
};
|
||||
roadSegments: {
|
||||
analyzed: number;
|
||||
roads: {
|
||||
analyzed: any;
|
||||
closed: any;
|
||||
};
|
||||
candidateRoads: {
|
||||
total: number;
|
||||
pending: number;
|
||||
approved: number;
|
||||
rejected: number;
|
||||
candidates: {
|
||||
total: any;
|
||||
pending: any;
|
||||
};
|
||||
}>;
|
||||
syncExternal(): Promise<{
|
||||
imported: number;
|
||||
}>;
|
||||
syncExternal(): Promise<void>;
|
||||
refreshCache(): Promise<{
|
||||
cachedCount: number;
|
||||
cachedCount: any;
|
||||
}>;
|
||||
analyzeRoadSpeeds(hours?: number): Promise<{
|
||||
segmentsUpdated: number;
|
||||
totalPointsProcessed: number;
|
||||
topAdjustments: any[];
|
||||
analyzeRoadSpeeds(hours?: number): Promise<any>;
|
||||
discoverNewRoads(hours?: number): Promise<any>;
|
||||
getCandidates(status?: string): Promise<import("../maps/candidate-road.entity").CandidateRoad[]>;
|
||||
discoverClosures(hours?: number): Promise<{
|
||||
roadsClosed: any;
|
||||
}>;
|
||||
discoverNewRoads(hours?: number): Promise<{
|
||||
candidatesFound: number;
|
||||
candidates: any[];
|
||||
}>;
|
||||
getCongestionData(north: number, south: number, east: number, west: number): Promise<any[]>;
|
||||
getClosures(): Promise<import("../maps/road-stat.entity").RoadSegmentStat[]>;
|
||||
getCongestionData(north: number, south: number, east: number, west: number): Promise<any>;
|
||||
approveCandidate(id: string): Promise<{
|
||||
message: string;
|
||||
success: boolean;
|
||||
id: string;
|
||||
status: string;
|
||||
}>;
|
||||
rejectCandidate(id: string): Promise<{
|
||||
message: string;
|
||||
success: boolean;
|
||||
id: string;
|
||||
status: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
+38
-2
@@ -16,6 +16,7 @@ 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");
|
||||
const api_key_guard_1 = require("../common/guards/api-key.guard");
|
||||
let MapRefinementController = class MapRefinementController {
|
||||
analyzerService;
|
||||
constructor(analyzerService) {
|
||||
@@ -38,6 +39,15 @@ let MapRefinementController = class MapRefinementController {
|
||||
const sinceHours = hours ? Number(hours) : 168;
|
||||
return this.analyzerService.discoverNewRoads(sinceHours);
|
||||
}
|
||||
async getCandidates(status) {
|
||||
return this.analyzerService.getCandidates(status || 'pending');
|
||||
}
|
||||
async discoverClosures(hours) {
|
||||
return this.analyzerService.discoverRoadClosures(hours || 48);
|
||||
}
|
||||
async getClosures() {
|
||||
return this.analyzerService.getClosures();
|
||||
}
|
||||
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);
|
||||
@@ -50,10 +60,10 @@ let MapRefinementController = class MapRefinementController {
|
||||
});
|
||||
}
|
||||
async approveCandidate(id) {
|
||||
return { message: `Candidate ${id} approved`, status: 'approved' };
|
||||
return this.analyzerService.updateCandidateStatus(id, 'approved');
|
||||
}
|
||||
async rejectCandidate(id) {
|
||||
return { message: `Candidate ${id} rejected`, status: 'rejected' };
|
||||
return this.analyzerService.updateCandidateStatus(id, 'rejected');
|
||||
}
|
||||
};
|
||||
exports.MapRefinementController = MapRefinementController;
|
||||
@@ -96,6 +106,31 @@ __decorate([
|
||||
__metadata("design:paramtypes", [Number]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], MapRefinementController.prototype, "discoverNewRoads", null);
|
||||
__decorate([
|
||||
(0, common_1.Get)('candidates'),
|
||||
(0, swagger_1.ApiOperation)({ summary: 'Get list of candidate roads / جلب الطرق المرشحة' }),
|
||||
(0, swagger_1.ApiQuery)({ name: 'status', required: false, enum: ['pending', 'approved', 'rejected'] }),
|
||||
__param(0, (0, common_1.Query)('status')),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], MapRefinementController.prototype, "getCandidates", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('discover-closures'),
|
||||
(0, swagger_1.ApiOperation)({ summary: 'Run road closure detection 🚧 / تشغيل كشف الإغلاقات' }),
|
||||
(0, swagger_1.ApiQuery)({ name: 'hours', required: false, description: 'Hours to look back (default: 48)' }),
|
||||
__param(0, (0, common_1.Query)('hours')),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Number]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], MapRefinementController.prototype, "discoverClosures", null);
|
||||
__decorate([
|
||||
(0, common_1.Get)('closures'),
|
||||
(0, swagger_1.ApiOperation)({ summary: 'Get currently detected closures / جلب الإغلاقات المكتشفة' }),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:returntype", Promise)
|
||||
], MapRefinementController.prototype, "getClosures", null);
|
||||
__decorate([
|
||||
(0, common_1.Get)('congestion'),
|
||||
(0, swagger_1.ApiOperation)({ summary: 'Get congestion data for heatmap 🗺️ / بيانات الازدحام' }),
|
||||
@@ -130,6 +165,7 @@ __decorate([
|
||||
exports.MapRefinementController = MapRefinementController = __decorate([
|
||||
(0, swagger_1.ApiTags)('map-refinement'),
|
||||
(0, common_1.Controller)('map-refinement'),
|
||||
(0, common_1.UseGuards)(api_key_guard_1.ApiKeyGuard),
|
||||
__metadata("design:paramtypes", [telemetry_analyzer_service_1.TelemetryAnalyzerService])
|
||||
], MapRefinementController);
|
||||
//# sourceMappingURL=map-refinement.controller.js.map
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"map-refinement.controller.js","sourceRoot":"","sources":["../../src/telemetry/map-refinement.controller.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"map-refinement.controller.js","sourceRoot":"","sources":["../../src/telemetry/map-refinement.controller.ts"],"names":[],"mappings":"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"}
|
||||
+38
-33
@@ -2,55 +2,60 @@ 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';
|
||||
import { RedisService } from '../common/redis.service';
|
||||
import { TelegramService } from '../common/telegram.service';
|
||||
import { TrafficGridService } from '../maps/traffic-grid.service';
|
||||
export declare class TelemetryAnalyzerService {
|
||||
private telemetryRepo;
|
||||
private roadStatRepo;
|
||||
private candidateRoadRepo;
|
||||
private dataSource;
|
||||
private redisService;
|
||||
private externalTelemetry;
|
||||
private redisService;
|
||||
private telegramService;
|
||||
private trafficGrid;
|
||||
private dataSource;
|
||||
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;
|
||||
constructor(telemetryRepo: Repository<TelemetryLog>, roadStatRepo: Repository<RoadSegmentStat>, candidateRoadRepo: Repository<CandidateRoad>, externalTelemetry: ExternalTelemetryService, redisService: RedisService, telegramService: TelegramService, trafficGrid: TrafficGridService, dataSource: DataSource);
|
||||
runDeepIntelligence(hours?: number): Promise<{
|
||||
success: boolean;
|
||||
timestamp: string;
|
||||
speedAnalysis: any;
|
||||
temporalAnalysis: any;
|
||||
roadDiscovery: any;
|
||||
}>;
|
||||
handleDeepIntelligence(): void;
|
||||
syncExternalData(days?: number): Promise<void>;
|
||||
refreshTrafficCache(): Promise<{
|
||||
cachedCount: number;
|
||||
cachedCount: any;
|
||||
}>;
|
||||
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[]>;
|
||||
analyzeRoadSpeeds(sinceHours?: number): Promise<any>;
|
||||
analyzeTimeProfiles(sinceHours?: number): Promise<any>;
|
||||
discoverNewRoads(sinceHours?: number): Promise<any>;
|
||||
getCongestionData(bounds: any): Promise<any>;
|
||||
getAnalysisSummary(): Promise<{
|
||||
telemetry: {
|
||||
totalPoints: number;
|
||||
last24h: number;
|
||||
activeDrivers24h: number;
|
||||
total: any;
|
||||
};
|
||||
roadSegments: {
|
||||
analyzed: number;
|
||||
roads: {
|
||||
analyzed: any;
|
||||
closed: any;
|
||||
};
|
||||
candidateRoads: {
|
||||
total: number;
|
||||
pending: number;
|
||||
approved: number;
|
||||
rejected: number;
|
||||
candidates: {
|
||||
total: any;
|
||||
pending: any;
|
||||
};
|
||||
}>;
|
||||
getCandidates(status?: string, limit?: number): Promise<CandidateRoad[]>;
|
||||
updateCandidateStatus(id: string, status: string): Promise<{
|
||||
success: boolean;
|
||||
id: string;
|
||||
status: string;
|
||||
}>;
|
||||
discoverRoadClosures(sinceHours?: number): Promise<{
|
||||
roadsClosed: any;
|
||||
}>;
|
||||
getClosures(): Promise<RoadSegmentStat[]>;
|
||||
private calculateConfidence;
|
||||
}
|
||||
|
||||
+301
-248
@@ -15,309 +15,360 @@ var TelemetryAnalyzerService_1;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.TelemetryAnalyzerService = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const schedule_1 = require("@nestjs/schedule");
|
||||
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");
|
||||
const redis_service_1 = require("../common/redis.service");
|
||||
const telegram_service_1 = require("../common/telegram.service");
|
||||
const traffic_grid_service_1 = require("../maps/traffic-grid.service");
|
||||
let TelemetryAnalyzerService = TelemetryAnalyzerService_1 = class TelemetryAnalyzerService {
|
||||
telemetryRepo;
|
||||
roadStatRepo;
|
||||
candidateRoadRepo;
|
||||
dataSource;
|
||||
redisService;
|
||||
externalTelemetry;
|
||||
redisService;
|
||||
telegramService;
|
||||
trafficGrid;
|
||||
dataSource;
|
||||
logger = new common_1.Logger(TelemetryAnalyzerService_1.name);
|
||||
TRAFFIC_CACHE_KEY = 'traffic_snapshot';
|
||||
constructor(telemetryRepo, roadStatRepo, candidateRoadRepo, dataSource, redisService, externalTelemetry) {
|
||||
TRAFFIC_CACHE_KEY = 'live_traffic_congested';
|
||||
constructor(telemetryRepo, roadStatRepo, candidateRoadRepo, externalTelemetry, redisService, telegramService, trafficGrid, dataSource) {
|
||||
this.telemetryRepo = telemetryRepo;
|
||||
this.roadStatRepo = roadStatRepo;
|
||||
this.candidateRoadRepo = candidateRoadRepo;
|
||||
this.dataSource = dataSource;
|
||||
this.redisService = redisService;
|
||||
this.externalTelemetry = externalTelemetry;
|
||||
this.redisService = redisService;
|
||||
this.telegramService = telegramService;
|
||||
this.trafficGrid = trafficGrid;
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
async handleNightlyIntelligence() {
|
||||
this.logger.log('⏰ Starting automated 3 AM intelligence process...');
|
||||
async runDeepIntelligence(hours = 48) {
|
||||
this.logger.log(`🚀 Starting Full Intelligence Pipeline (Window: ${hours}h)...`);
|
||||
await this.syncExternalData(Math.ceil(hours / 24));
|
||||
const speedResult = await this.analyzeRoadSpeeds(hours);
|
||||
const temporalResult = await this.analyzeTimeProfiles(hours);
|
||||
const discoveryResult = await this.discoverNewRoads(hours);
|
||||
await this.refreshTrafficCache();
|
||||
await this.trafficGrid.refreshGrid();
|
||||
const summary = await this.getAnalysisSummary();
|
||||
await this.telegramService.sendIntelligenceReport({
|
||||
syncResult: speedResult.totalPointsProcessed || 0,
|
||||
updatedSegments: speedResult.segmentsUpdated || 0,
|
||||
discoveredRoads: discoveryResult.candidatesFound || 0,
|
||||
timeProfiles: temporalResult.bucketsUpdated || 0,
|
||||
totalPoints: summary.telemetry.total,
|
||||
days: Math.ceil(hours / 24),
|
||||
});
|
||||
this.logger.log('🏁 Intelligence Pipeline Finished Successfully.');
|
||||
return {
|
||||
success: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
speedAnalysis: speedResult,
|
||||
temporalAnalysis: temporalResult,
|
||||
roadDiscovery: discoveryResult,
|
||||
};
|
||||
}
|
||||
handleDeepIntelligence() {
|
||||
this.runDeepIntelligence(240);
|
||||
}
|
||||
async syncExternalData(days = 7) {
|
||||
this.logger.log(`📡 Starting batch sync (Window: ${days} days)...`);
|
||||
try {
|
||||
await this.syncExternalData(1);
|
||||
await this.analyzeRoadSpeeds(24);
|
||||
await this.discoverNewRoads(168);
|
||||
await this.refreshTrafficCache();
|
||||
this.logger.log('✅ 3 AM intelligence process complete.');
|
||||
const tracks = await this.externalTelemetry.fetchCarTracks(days);
|
||||
if (tracks.length === 0)
|
||||
return;
|
||||
this.logger.log(`📥 Saving ${tracks.length} points to database...`);
|
||||
const values = tracks
|
||||
.filter(t => t.driver_id && t.longitude && t.latitude)
|
||||
.map(t => `('${t.driver_id}', ST_SetSRID(ST_Point(${t.longitude}, ${t.latitude}), 4326), ${t.latitude}, ${t.longitude}, ${t.speed || 0}, ${t.heading || 0}, '${t.created_at}')`).join(',');
|
||||
await this.dataSource.query(`
|
||||
INSERT INTO telemetry_logs ("driverId", location, latitude, longitude, speed, heading, timestamp)
|
||||
VALUES ${values}
|
||||
ON CONFLICT DO NOTHING
|
||||
`);
|
||||
this.logger.log(`✅ Batch sync complete.`);
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('❌ Nightly intelligence failed:', error.stack);
|
||||
this.logger.error(`❌ Sync failed: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
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...');
|
||||
this.logger.log('🚀 Refreshing Redis traffic snapshot (v2.5.2 Optimized)...');
|
||||
const congested = await this.roadStatRepo.query(`
|
||||
SELECT
|
||||
"segmentId",
|
||||
"congestionFactor",
|
||||
ST_AsGeoJSON(geometry) as geojson
|
||||
SELECT "segmentId", "congestionFactor", ST_AsGeoJSON(geometry, 5) as geojson
|
||||
FROM road_segment_stats
|
||||
WHERE "congestionFactor" > 1.1
|
||||
ORDER BY "congestionFactor" DESC
|
||||
LIMIT 2000
|
||||
`);
|
||||
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.`);
|
||||
const snapshot = congested.map(row => ({
|
||||
sid: row.segmentId,
|
||||
cf: parseFloat(row.congestionFactor),
|
||||
geo: JSON.parse(row.geojson)
|
||||
}));
|
||||
const sampleSize = Math.min(congested.length, 3);
|
||||
const topSegments = congested.slice(0, sampleSize).map(s => `${s.segmentId} (F: ${s.congestionFactor})`).join(', ');
|
||||
const totalSizeKB = Math.round(JSON.stringify(snapshot).length / 1024);
|
||||
this.logger.log(`📊 Traffic Snapshot Sample (Top ${sampleSize}): ${topSegments}`);
|
||||
this.logger.log(`📦 Redis Payload Size: ~${totalSizeKB} KB`);
|
||||
await this.redisService.set(this.TRAFFIC_CACHE_KEY, snapshot);
|
||||
this.logger.log(`✅ Redis traffic snapshot updated with ${congested.length} segments.`);
|
||||
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
|
||||
this.logger.log(`🔍 Starting road speed analysis v2.5 for last ${sinceHours}h...`);
|
||||
const query = `
|
||||
WITH grid_points AS (
|
||||
-- Group by 5m grid cell first to reduce spatial join volume (v2.5.1 WoW Performance)
|
||||
SELECT ST_SnapToGrid(ST_Transform(location::geometry, 3857), 5) AS loc,
|
||||
AVG(speed) as speed
|
||||
FROM telemetry_logs
|
||||
WHERE timestamp >= NOW() - INTERVAL '${sinceHours} hours' AND speed > 2
|
||||
GROUP BY loc
|
||||
),
|
||||
matches AS (
|
||||
-- Bulk Spatial Join (20m radius) with GIST optimization
|
||||
SELECT l.osm_id::text as id, l.name, l.highway, g.speed, l.way
|
||||
FROM grid_points g
|
||||
INNER JOIN planet_osm_line l ON
|
||||
l.highway IS NOT NULL AND
|
||||
l.way && ST_Expand(g.loc, 20) AND
|
||||
ST_DWithin(l.way, g.loc, 20)
|
||||
),
|
||||
stats AS (
|
||||
-- Calculate median speed and aggregate samples
|
||||
SELECT id, name, highway,
|
||||
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY speed) as avg_speed,
|
||||
COUNT(*) as samples,
|
||||
ST_Transform(MIN(way), 4326) as way4326
|
||||
FROM matches
|
||||
GROUP BY id, name, highway
|
||||
HAVING COUNT(*) >= 3
|
||||
)
|
||||
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)
|
||||
}))
|
||||
};
|
||||
INSERT INTO road_segment_stats ("segmentId", "averageSpeed", "sampleCount", "lastUpdated", geometry, "congestionFactor")
|
||||
SELECT id, avg_speed, samples, NOW(), way4326,
|
||||
CASE WHEN highway IN ('primary','secondary','trunk','motorway') AND avg_speed < 30
|
||||
THEN 30/GREATEST(avg_speed,1) ELSE 1.0 END
|
||||
FROM stats
|
||||
ON CONFLICT ("segmentId") DO UPDATE SET
|
||||
"averageSpeed"=EXCLUDED."averageSpeed",
|
||||
"sampleCount"=EXCLUDED."sampleCount",
|
||||
"lastUpdated"=NOW(),
|
||||
"geometry"=EXCLUDED."geometry",
|
||||
"congestionFactor"=EXCLUDED."congestionFactor"
|
||||
RETURNING "segmentId";
|
||||
`;
|
||||
const result = await this.dataSource.query(query);
|
||||
return { segmentsUpdated: result.length };
|
||||
}
|
||||
async analyzeTimeProfiles(sinceHours = 720) {
|
||||
this.logger.log(`🕒 Starting temporal profiling (Phase 2) for last ${sinceHours}h...`);
|
||||
const query = `
|
||||
WITH grid_points AS (
|
||||
-- Bucket by 10m grid and Time (Hour + DOW)
|
||||
SELECT
|
||||
ST_SnapToGrid(ST_Transform(location::geometry, 3857), 10) AS loc,
|
||||
EXTRACT(HOUR FROM timestamp)::int as hr,
|
||||
EXTRACT(DOW FROM timestamp)::int as dow,
|
||||
AVG(speed) as speed
|
||||
FROM telemetry_logs
|
||||
WHERE timestamp >= NOW() - INTERVAL '${sinceHours} hours' AND speed > 2
|
||||
GROUP BY loc, hr, dow
|
||||
),
|
||||
matches AS (
|
||||
-- Map to OSM segments
|
||||
SELECT l.osm_id::text as sid, g.hr, g.dow, g.speed
|
||||
FROM grid_points g
|
||||
INNER JOIN planet_osm_line l ON
|
||||
l.highway IS NOT NULL AND
|
||||
l.way && ST_Expand(g.loc, 20) AND
|
||||
ST_DWithin(l.way, g.loc, 20)
|
||||
),
|
||||
temporal_stats AS (
|
||||
-- Aggregate by segment + time bucket
|
||||
SELECT sid, hr, dow,
|
||||
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY speed) as avg_spd,
|
||||
COUNT(*) as smp
|
||||
FROM matches
|
||||
GROUP BY sid, hr, dow
|
||||
HAVING COUNT(*) >= 2 -- Require minimum samples per bucket
|
||||
)
|
||||
INSERT INTO road_speed_profiles ("segmentId", "hourOfDay", "dayOfWeek", "averageSpeed", "sampleCount", "lastUpdated")
|
||||
SELECT sid, hr, dow, avg_spd, smp, NOW()
|
||||
FROM temporal_stats
|
||||
ON CONFLICT ("segmentId", "hourOfDay", "dayOfWeek") DO UPDATE SET
|
||||
"averageSpeed" = EXCLUDED."averageSpeed",
|
||||
"sampleCount" = EXCLUDED."sampleCount",
|
||||
"lastUpdated" = NOW()
|
||||
RETURNING "segmentId";
|
||||
`;
|
||||
const result = await this.dataSource.query(query);
|
||||
this.logger.log(`📊 Time-aware profiling complete: ${result.length} buckets updated.`);
|
||||
return { bucketsUpdated: result.length };
|
||||
}
|
||||
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)
|
||||
)
|
||||
this.logger.log(`🛣️ Starting road discovery v2.5 for last ${sinceHours}h...`);
|
||||
const query = `
|
||||
WITH grid_points AS (
|
||||
-- Group by cell and driver to reduce volume for clustering and anti-join (v2.5.1 WoW Performance)
|
||||
SELECT "driverId", ST_SnapToGrid(ST_Transform(location::geometry, 3857), 10) as loc,
|
||||
AVG(speed) as speed,
|
||||
MIN(timestamp) as timestamp
|
||||
FROM telemetry_logs
|
||||
WHERE timestamp >= NOW() - INTERVAL '${sinceHours} hours' AND speed > 5
|
||||
GROUP BY loc, "driverId"
|
||||
),
|
||||
off_road AS (
|
||||
-- Spatial Anti-Join: Only points further than 35m from any existing highway
|
||||
SELECT g.* FROM grid_points g
|
||||
LEFT JOIN planet_osm_line l ON
|
||||
l.highway IS NOT NULL AND
|
||||
l.way && ST_Expand(g.loc, 35) AND
|
||||
ST_DWithin(l.way, g.loc, 35)
|
||||
WHERE l.osm_id IS NULL
|
||||
),
|
||||
-- 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
|
||||
-- Density-based clustering to find linear paths
|
||||
SELECT *, ST_ClusterDBSCAN(loc, eps := 30, minpoints := 3) OVER () as cid FROM off_road
|
||||
),
|
||||
cluster_stats AS (
|
||||
-- 1. Calculate cluster-wide quality metrics
|
||||
SELECT cid,
|
||||
COUNT(DISTINCT "driverId") as drv_count,
|
||||
COUNT(*) as pt_count,
|
||||
AVG(speed) as avg_spd
|
||||
FROM clustered
|
||||
WHERE cid IS NOT NULL
|
||||
GROUP BY cid
|
||||
HAVING COUNT(DISTINCT "driverId") >= 3 AND COUNT(*) >= 15
|
||||
),
|
||||
cluster_path AS (
|
||||
-- 2. Extract unique grid cells per cluster in chronological order
|
||||
SELECT cid, loc, MIN(timestamp) as ts
|
||||
FROM clustered
|
||||
WHERE cid IN (SELECT cid FROM cluster_stats)
|
||||
GROUP BY cid, loc
|
||||
),
|
||||
final_candidates AS (
|
||||
-- 3. Build geometry and calculate final scoring
|
||||
SELECT
|
||||
s.cid,
|
||||
ST_Transform(ST_MakeLine(p.loc ORDER BY p.ts), 4326) as geom,
|
||||
s.drv_count,
|
||||
s.pt_count,
|
||||
s.avg_spd,
|
||||
ST_Length(ST_Transform(ST_MakeLine(p.loc ORDER BY p.ts), 4326)::geography) as len
|
||||
FROM cluster_stats s
|
||||
JOIN cluster_path p ON s.cid = p.cid
|
||||
GROUP BY s.cid, s.drv_count, s.pt_count, s.avg_spd
|
||||
)
|
||||
-- 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,
|
||||
};
|
||||
INSERT INTO candidate_roads (geometry, "uniqueDriverCount", "totalPoints", "averageSpeed", "lengthMeters", confidence, status)
|
||||
SELECT geom, drv_count, pt_count, avg_spd, len,
|
||||
-- SQL port of calculateConfidence logic
|
||||
ROUND((
|
||||
LEAST(drv_count::float / 5, 1.0) * 0.4 +
|
||||
LEAST(pt_count::float / 50, 1.0) * 0.3 +
|
||||
CASE WHEN len BETWEEN 50 AND 2000 THEN 0.3 ELSE 0.1 END
|
||||
)::numeric, 2) as conf,
|
||||
'pending'
|
||||
FROM final_candidates
|
||||
RETURNING id;
|
||||
`;
|
||||
const result = await this.dataSource.query(query);
|
||||
return { candidatesFound: result.length };
|
||||
}
|
||||
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
|
||||
SELECT rs."segmentId", rs."averageSpeed", rs."congestionFactor", rs."sampleCount", 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
|
||||
WHERE rs.geometry IS NOT NULL AND ST_Intersects(rs.geometry::geometry, ST_MakeEnvelope($1, $2, $3, $4, 4326))
|
||||
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'
|
||||
`);
|
||||
const [tCount] = await this.dataSource.query('SELECT COUNT(*) as count FROM telemetry_logs');
|
||||
const [rCount] = await this.dataSource.query('SELECT COUNT(*) as count FROM road_segment_stats');
|
||||
const [cCount] = await this.dataSource.query('SELECT COUNT(*) as count, COUNT(*) FILTER (WHERE status=\'pending\') as p FROM candidate_roads');
|
||||
const [clCount] = await this.dataSource.query('SELECT COUNT(*) as count FROM road_segment_stats WHERE "isClosed" = true');
|
||||
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'),
|
||||
},
|
||||
telemetry: { total: tCount.count },
|
||||
roads: { analyzed: rCount.count, closed: clCount.count },
|
||||
candidates: { total: cCount.count, pending: cCount.p }
|
||||
};
|
||||
}
|
||||
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;
|
||||
async getCandidates(status = 'pending', limit = 50) {
|
||||
return this.candidateRoadRepo.find({
|
||||
where: { status },
|
||||
order: { confidence: 'DESC' },
|
||||
take: limit
|
||||
});
|
||||
}
|
||||
async updateCandidateStatus(id, status) {
|
||||
await this.candidateRoadRepo.update(id, {
|
||||
status,
|
||||
reviewedAt: new Date()
|
||||
});
|
||||
return { success: true, id, status };
|
||||
}
|
||||
async discoverRoadClosures(sinceHours = 48) {
|
||||
this.logger.log(`🚧 Analyzing road closures for last ${sinceHours}h...`);
|
||||
await this.roadStatRepo.update({ isClosed: true }, { isClosed: false });
|
||||
const query = `
|
||||
WITH active_area AS (
|
||||
-- Bounding box of some recent activity to prove drivers are on the map
|
||||
SELECT ST_Expand(ST_Extent(location::geometry), 0.01) as bbox
|
||||
FROM telemetry_logs
|
||||
WHERE timestamp >= NOW() - INTERVAL '${sinceHours} hours'
|
||||
),
|
||||
possible_closures AS (
|
||||
SELECT rs."segmentId"
|
||||
FROM road_segment_stats rs
|
||||
WHERE rs."sampleCount" > 50
|
||||
AND rs.geometry && (SELECT bbox FROM active_area)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM telemetry_logs t
|
||||
WHERE t.timestamp >= NOW() - INTERVAL '${sinceHours} hours'
|
||||
AND ST_DWithin(rs.geometry::geometry, t.location::geometry, 35)
|
||||
)
|
||||
)
|
||||
UPDATE road_segment_stats
|
||||
SET "isClosed" = true
|
||||
WHERE "segmentId" IN (SELECT "segmentId" FROM possible_closures)
|
||||
RETURNING "segmentId";
|
||||
`;
|
||||
const result = await this.dataSource.query(query);
|
||||
this.logger.log(`✅ Road closure detection complete: ${result.length} roads flagged as closed.`);
|
||||
return { roadsClosed: result.length };
|
||||
}
|
||||
async getClosures() {
|
||||
return this.roadStatRepo.find({
|
||||
where: { isClosed: true },
|
||||
order: { sampleCount: 'DESC' }
|
||||
});
|
||||
}
|
||||
calculateConfidence(drivers, points, len) {
|
||||
const dScore = Math.min(drivers / 5, 1.0) * 0.4;
|
||||
const pScore = Math.min(points / 50, 1.0) * 0.3;
|
||||
const lScore = (len >= 50 && len <= 2000) ? 0.3 : 0.1;
|
||||
return Math.round((dScore + pScore + lScore) * 100) / 100;
|
||||
}
|
||||
};
|
||||
exports.TelemetryAnalyzerService = TelemetryAnalyzerService;
|
||||
__decorate([
|
||||
(0, schedule_1.Cron)('0 3 * * *'),
|
||||
(0, schedule_1.Cron)('0 4 */10 * *'),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:returntype", Promise)
|
||||
], TelemetryAnalyzerService.prototype, "handleNightlyIntelligence", null);
|
||||
__metadata("design:returntype", void 0)
|
||||
], TelemetryAnalyzerService.prototype, "handleDeepIntelligence", null);
|
||||
exports.TelemetryAnalyzerService = TelemetryAnalyzerService = TelemetryAnalyzerService_1 = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__param(0, (0, typeorm_1.InjectRepository)(telemetry_entity_1.TelemetryLog)),
|
||||
@@ -326,8 +377,10 @@ exports.TelemetryAnalyzerService = TelemetryAnalyzerService = TelemetryAnalyzerS
|
||||
__metadata("design:paramtypes", [typeorm_2.Repository,
|
||||
typeorm_2.Repository,
|
||||
typeorm_2.Repository,
|
||||
typeorm_2.DataSource,
|
||||
external_telemetry_service_1.ExternalTelemetryService,
|
||||
redis_service_1.RedisService,
|
||||
external_telemetry_service_1.ExternalTelemetryService])
|
||||
telegram_service_1.TelegramService,
|
||||
traffic_grid_service_1.TrafficGridService,
|
||||
typeorm_2.DataSource])
|
||||
], TelemetryAnalyzerService);
|
||||
//# sourceMappingURL=telemetry-analyzer.service.js.map
|
||||
File diff suppressed because one or more lines are too long
+15
-52
@@ -8,69 +8,32 @@ export declare class TelemetryController {
|
||||
success: boolean;
|
||||
timestamp: Date;
|
||||
}>;
|
||||
sync(): Promise<{
|
||||
imported: number;
|
||||
}>;
|
||||
sync(): Promise<void>;
|
||||
analyze(hours?: number): Promise<{
|
||||
success: boolean;
|
||||
roadAnalysis: {
|
||||
segmentsUpdated: number;
|
||||
totalPointsProcessed: number;
|
||||
topAdjustments: any[];
|
||||
};
|
||||
roadDiscovery: {
|
||||
candidatesFound: number;
|
||||
candidates: any[];
|
||||
};
|
||||
roadAnalysis: any;
|
||||
roadDiscovery: any;
|
||||
}>;
|
||||
processIntelligence(hours?: number): Promise<{
|
||||
processIntelligence(hours?: string, days?: string): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
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;
|
||||
};
|
||||
window: {
|
||||
days: number;
|
||||
hours: number;
|
||||
};
|
||||
}>;
|
||||
getSummary(): Promise<{
|
||||
telemetry: {
|
||||
totalPoints: number;
|
||||
last24h: number;
|
||||
activeDrivers24h: number;
|
||||
total: any;
|
||||
};
|
||||
roadSegments: {
|
||||
analyzed: number;
|
||||
roads: {
|
||||
analyzed: any;
|
||||
closed: any;
|
||||
};
|
||||
candidateRoads: {
|
||||
total: number;
|
||||
pending: number;
|
||||
approved: number;
|
||||
rejected: number;
|
||||
candidates: {
|
||||
total: any;
|
||||
pending: any;
|
||||
};
|
||||
}>;
|
||||
getNearby(lat: number, lng: number, radius: number): Promise<import("./telemetry.entity").TelemetryLog[]>;
|
||||
|
||||
+12
-14
@@ -13,10 +13,11 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
||||
};
|
||||
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");
|
||||
const api_key_guard_1 = require("../common/guards/api-key.guard");
|
||||
const common_1 = require("@nestjs/common");
|
||||
const swagger_1 = require("@nestjs/swagger");
|
||||
let TelemetryController = class TelemetryController {
|
||||
telemetryService;
|
||||
analyzerService;
|
||||
@@ -39,20 +40,15 @@ let TelemetryController = class TelemetryController {
|
||||
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();
|
||||
async processIntelligence(hours, days) {
|
||||
const parsedDays = days ? parseInt(days, 10) : 1;
|
||||
const parsedHours = hours ? parseInt(hours, 10) : (parsedDays * 24);
|
||||
this.analyzerService.runDeepIntelligence(parsedHours);
|
||||
return {
|
||||
success: true,
|
||||
message: `Intelligence sync and analysis started for the last ${parsedDays} days. You will receive a summary on Telegram once complete! 🚀`,
|
||||
timestamp: new Date(),
|
||||
sync: syncResults,
|
||||
analysis: {
|
||||
roads: roadResults,
|
||||
discovery: discoveryResults,
|
||||
},
|
||||
currentStatus: summary,
|
||||
window: { days: parsedDays, hours: parsedHours },
|
||||
};
|
||||
}
|
||||
async getSummary() {
|
||||
@@ -90,8 +86,9 @@ __decorate([
|
||||
(0, common_1.Post)('process-intelligence'),
|
||||
(0, swagger_1.ApiOperation)({ summary: 'Automated One-Click Sync & Analyze 🚀' }),
|
||||
__param(0, (0, common_1.Query)('hours')),
|
||||
__param(1, (0, common_1.Query)('days')),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Number]),
|
||||
__metadata("design:paramtypes", [String, String]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], TelemetryController.prototype, "processIntelligence", null);
|
||||
__decorate([
|
||||
@@ -114,6 +111,7 @@ __decorate([
|
||||
exports.TelemetryController = TelemetryController = __decorate([
|
||||
(0, swagger_1.ApiTags)('telemetry'),
|
||||
(0, common_1.Controller)('telemetry'),
|
||||
(0, common_1.UseGuards)(api_key_guard_1.ApiKeyGuard),
|
||||
__metadata("design:paramtypes", [telemetry_service_1.TelemetryService,
|
||||
telemetry_analyzer_service_1.TelemetryAnalyzerService])
|
||||
], TelemetryController);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user