feat: implement manual routing sync system, update map tile URLs to HTTPS, and migrate admin session storage to localStorage
This commit is contained in:
@@ -112,6 +112,58 @@ export class GeocodingService {
|
||||
ORDER BY (o.name <-> $1) ASC LIMIT 10
|
||||
`, [cleanQuery, lat || null, lon || null, radius]));
|
||||
|
||||
queryPromises.push(this.osmPointsRepository.query(`
|
||||
SELECT
|
||||
id, name_ar as name, name_ar, name_en, 'admin' as category,
|
||||
'' as neighbourhood, '' as district, '' as governorate,
|
||||
ST_Y(ST_Centroid(geom))::text as latitude, ST_X(ST_Centroid(geom))::text as longitude, '' as address, 'admin_boundary' as region, 'admin' as source,
|
||||
CASE WHEN $2::float IS NOT NULL THEN ST_DistanceSphere(geom, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) ELSE 0 END as distance,
|
||||
similarity(COALESCE(name_ar, ''), $1) as relevance
|
||||
FROM admin_boundaries
|
||||
WHERE name_ar % $1
|
||||
AND ($2::float IS NULL OR ST_DistanceSphere(geom, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) <= $4)
|
||||
ORDER BY (name_ar <-> $1) ASC LIMIT 5
|
||||
`, [cleanQuery, lat || null, lon || null, radius]));
|
||||
|
||||
queryPromises.push(this.osmPointsRepository.query(`
|
||||
SELECT
|
||||
id::text, COALESCE(names->>'primary', names->>'common', 'Street') as name, COALESCE(names->>'primary', names->>'common', 'Street') as name_ar, '' as name_en, 'street' as category,
|
||||
'' as neighbourhood, '' as district, '' as governorate,
|
||||
ST_Y(ST_Centroid(location))::text as latitude, ST_X(ST_Centroid(location))::text as longitude, '' as address, 'overture' as region, 'overture_global' as source,
|
||||
CASE WHEN $2::float IS NOT NULL THEN ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) ELSE 0 END as distance,
|
||||
GREATEST(similarity(COALESCE(names->>'primary', ''), $1), similarity(COALESCE(names->>'common', ''), $1)) as relevance
|
||||
FROM overture_segment
|
||||
WHERE (names->>'primary' % $1 OR names->>'common' % $1)
|
||||
AND ($2::float IS NULL OR ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) <= $4)
|
||||
ORDER BY (COALESCE(names->>'primary', '') <-> $1) ASC LIMIT 10
|
||||
`, [cleanQuery, lat || null, lon || null, radius]));
|
||||
|
||||
queryPromises.push(this.osmPointsRepository.query(`
|
||||
SELECT
|
||||
id::text, COALESCE(names->>'primary', names->>'common', 'Building') as name, COALESCE(names->>'primary', names->>'common', 'Building') as name_ar, '' as name_en, 'building' as category,
|
||||
'' as neighbourhood, '' as district, '' as governorate,
|
||||
ST_Y(ST_Centroid(location))::text as latitude, ST_X(ST_Centroid(location))::text as longitude, '' as address, 'overture' as region, 'overture_global' as source,
|
||||
CASE WHEN $2::float IS NOT NULL THEN ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) ELSE 0 END as distance,
|
||||
GREATEST(similarity(COALESCE(names->>'primary', ''), $1), similarity(COALESCE(names->>'common', ''), $1)) as relevance
|
||||
FROM overture_building
|
||||
WHERE (names->>'primary' % $1 OR names->>'common' % $1)
|
||||
AND ($2::float IS NULL OR ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) <= $4)
|
||||
ORDER BY (COALESCE(names->>'primary', '') <-> $1) ASC LIMIT 10
|
||||
`, [cleanQuery, lat || null, lon || null, radius]));
|
||||
|
||||
queryPromises.push(this.osmPointsRepository.query(`
|
||||
SELECT
|
||||
id::text, COALESCE(names->>'primary', names->>'common', 'Place') as name, COALESCE(names->>'primary', names->>'common', 'Place') as name_ar, '' as name_en, 'place' as category,
|
||||
'' as neighbourhood, '' as district, '' as governorate,
|
||||
ST_Y(ST_Centroid(location))::text as latitude, ST_X(ST_Centroid(location))::text as longitude, '' as address, 'overture' as region, 'overture_global' as source,
|
||||
CASE WHEN $2::float IS NOT NULL THEN ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) ELSE 0 END as distance,
|
||||
GREATEST(similarity(COALESCE(names->>'primary', ''), $1), similarity(COALESCE(names->>'common', ''), $1)) as relevance
|
||||
FROM overture_place
|
||||
WHERE (names->>'primary' % $1 OR names->>'common' % $1)
|
||||
AND ($2::float IS NULL OR ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) <= $4)
|
||||
ORDER BY (COALESCE(names->>'primary', '') <-> $1) ASC LIMIT 10
|
||||
`, [cleanQuery, lat || null, lon || null, radius]));
|
||||
|
||||
const executionResults = await Promise.race([
|
||||
Promise.allSettled(queryPromises),
|
||||
new Promise<any>((_, reject) => setTimeout(() => reject(new Error('QUERY_TIMEOUT')), this.DB_TIMEOUT_MS))
|
||||
@@ -184,7 +236,9 @@ export class GeocodingService {
|
||||
try {
|
||||
const repo = this.getRepositoryForCoords(lat, lng);
|
||||
const tableName = this.getTableNameForRepo(repo);
|
||||
const query = `
|
||||
const queryPromises: Promise<any[]>[] = [];
|
||||
|
||||
queryPromises.push(repo.query(`
|
||||
SELECT
|
||||
p.id, p.name, p.name_ar, p.category,
|
||||
n.name_ar as neighbourhood,
|
||||
@@ -197,9 +251,67 @@ export class GeocodingService {
|
||||
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
|
||||
`;
|
||||
return await repo.query(query, [lng, lat]);
|
||||
ORDER BY p.location::geometry <-> ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) ASC LIMIT 5
|
||||
`, [lng, lat]));
|
||||
|
||||
queryPromises.push(repo.query(`
|
||||
SELECT
|
||||
o.osm_id::text as id, o.name, o.name_ar, COALESCE(o.amenity, o.shop, 'place') as category,
|
||||
'' as neighbourhood, '' as district, '' as governorate,
|
||||
o.latitude, o.longitude, o.addr_street as address, 'osm_global' as source,
|
||||
ST_DistanceSphere(o.geom, ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326)) as distance
|
||||
FROM osm_points_with_area o
|
||||
WHERE o.geom IS NOT NULL AND (o.name IS NOT NULL OR o.name_ar IS NOT NULL)
|
||||
ORDER BY o.geom <-> ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) ASC LIMIT 5
|
||||
`, [lng, lat]));
|
||||
|
||||
queryPromises.push(repo.query(`
|
||||
SELECT
|
||||
id::text, COALESCE(names->>'primary', names->>'common', 'Street') as name, COALESCE(names->>'primary', names->>'common', 'Street') as name_ar, 'street' as category,
|
||||
'' as neighbourhood, '' as district, '' as governorate,
|
||||
ST_Y(ST_Centroid(location))::text as latitude, ST_X(ST_Centroid(location))::text as longitude, '' as address, 'overture_global' as source,
|
||||
ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326)) as distance
|
||||
FROM overture_segment
|
||||
WHERE location IS NOT NULL AND (names->>'primary' IS NOT NULL OR names->>'common' IS NOT NULL)
|
||||
ORDER BY location <-> ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) ASC LIMIT 5
|
||||
`, [lng, lat]));
|
||||
|
||||
queryPromises.push(repo.query(`
|
||||
SELECT
|
||||
id::text, COALESCE(names->>'primary', names->>'common', 'Building') as name, COALESCE(names->>'primary', names->>'common', 'Building') as name_ar, 'building' as category,
|
||||
'' as neighbourhood, '' as district, '' as governorate,
|
||||
ST_Y(ST_Centroid(location))::text as latitude, ST_X(ST_Centroid(location))::text as longitude, '' as address, 'overture_global' as source,
|
||||
ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326)) as distance
|
||||
FROM overture_building
|
||||
WHERE location IS NOT NULL AND (names->>'primary' IS NOT NULL OR names->>'common' IS NOT NULL)
|
||||
ORDER BY location <-> ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) ASC LIMIT 5
|
||||
`, [lng, lat]));
|
||||
|
||||
queryPromises.push(repo.query(`
|
||||
SELECT
|
||||
id::text, COALESCE(names->>'primary', names->>'common', 'Place') as name, COALESCE(names->>'primary', names->>'common', 'Place') as name_ar, 'place' as category,
|
||||
'' as neighbourhood, '' as district, '' as governorate,
|
||||
ST_Y(ST_Centroid(location))::text as latitude, ST_X(ST_Centroid(location))::text as longitude, '' as address, 'overture_global' as source,
|
||||
ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326)) as distance
|
||||
FROM overture_place
|
||||
WHERE location IS NOT NULL AND (names->>'primary' IS NOT NULL OR names->>'common' IS NOT NULL)
|
||||
ORDER BY location <-> ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) ASC LIMIT 5
|
||||
`, [lng, lat]));
|
||||
|
||||
const results = await Promise.allSettled(queryPromises);
|
||||
let allResults: any[] = [];
|
||||
results.forEach(res => {
|
||||
if (res.status === 'fulfilled' && res.value) allResults.push(...res.value);
|
||||
});
|
||||
|
||||
return allResults
|
||||
.sort((a, b) => Number(a.distance) - Number(b.distance))
|
||||
.slice(0, 5)
|
||||
.map(r => ({
|
||||
...r,
|
||||
latitude: parseFloat(r.latitude),
|
||||
longitude: parseFloat(r.longitude)
|
||||
}));
|
||||
} catch (error) {
|
||||
this.logger.error('Reverse geocoding error:', error);
|
||||
return [];
|
||||
@@ -252,6 +364,7 @@ export class GeocodingService {
|
||||
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
|
||||
UNION ALL SELECT id::text, COALESCE(names->>'primary', names->>'common', 'Place') as name, 'place' as category, ST_Y(ST_Centroid(location)) as latitude, ST_X(ST_Centroid(location)) as longitude, '' as address, 'overture' as region FROM overture_place WHERE names->>'primary' IS NOT NULL LIMIT 500
|
||||
`;
|
||||
const res = await this.placesSyriaRepository.query(q);
|
||||
const features = res.map(p => ({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Controller, Get, Query, UseGuards, Res } from '@nestjs/common';
|
||||
import { Controller, Get, Post, Query, UseGuards, Res } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation } from '@nestjs/swagger';
|
||||
import type { Response } from 'express';
|
||||
import * as fs from 'fs';
|
||||
@@ -12,6 +12,12 @@ import { ApiKeyGuard } from '../common/guards/api-key.guard';
|
||||
export class MapsController {
|
||||
constructor(private readonly mapsService: MapsService) { }
|
||||
|
||||
@Post('sync-routes')
|
||||
@ApiOperation({ summary: 'Request GraphHopper routing sync 🔄' })
|
||||
async syncRoutes() {
|
||||
return this.mapsService.requestRoutingSync();
|
||||
}
|
||||
|
||||
@Get('style.json')
|
||||
@ApiOperation({ summary: 'Get MapLibre style JSON 🎨' })
|
||||
async getStyleJson(@Query('theme') theme: string, @Res() res: Response) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import axios from 'axios';
|
||||
import { RoadSegmentStat } from './road-stat.entity';
|
||||
import { TrafficGridService } from './traffic-grid.service';
|
||||
import { GeocodingService } from '../geocoding/geocoding.service';
|
||||
import { RedisService } from '../common/redis.service';
|
||||
|
||||
@Injectable()
|
||||
export class MapsService {
|
||||
@@ -19,10 +20,16 @@ export class MapsService {
|
||||
private trafficGrid: TrafficGridService,
|
||||
private geocodingService: GeocodingService,
|
||||
private dataSource: DataSource,
|
||||
private redisService: RedisService,
|
||||
) {
|
||||
this.graphHopperUrl = this.configService.get('GRAPH_HOPPER_URL', 'http://routing:8080');
|
||||
}
|
||||
|
||||
async requestRoutingSync() {
|
||||
await this.redisService.set('routing_sync_requested', '1');
|
||||
return { success: true, message: 'Routing sync requested and scheduled for the next minute.' };
|
||||
}
|
||||
|
||||
|
||||
async getRoute(waypoints: [number, number][], profile: string = 'car', steps: boolean = false, locale: string = 'en', alternatives: boolean = false) {
|
||||
if (waypoints.length < 2) {
|
||||
|
||||
Reference in New Issue
Block a user