2026-04-13-4

This commit is contained in:
Hamza-Ayed
2026-04-13 20:02:57 +03:00
parent 5bbb5d8d8b
commit b36e197e09
21 changed files with 2236 additions and 899 deletions
+84 -15
View File
@@ -67,12 +67,21 @@ export class GeocodingService {
for (const tableName of userTables) {
let repo: Repository<any> = tableName === 'places_egypt' ? this.placesEgyptRepository : (tableName === 'places_jordan' ? this.placesJordanRepository : this.placesSyriaRepository);
const userQuery = `
SELECT id, name, name_ar, name_en, category, neighbourhood, latitude, longitude, address, '${tableName.replace('places_', '')}' as region, 'user_submitted' 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,
similarity(COALESCE(name_ar, ''), $1) + similarity(COALESCE(name, ''), $1) + similarity(COALESCE(neighbourhood, ''), $1) as relevance
FROM ${tableName}
WHERE (name_ar % $1 OR name % $1 OR neighbourhood % $1 OR name_ar ILIKE $4 OR name ILIKE $4 OR neighbourhood ILIKE $4)
${hasLocation ? `AND (location && ST_Expand(ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326), $5::float) OR ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) <= $6::float)` : ''}
SELECT
p.id, p.name, p.name_ar, p.name_en, p.category,
p.neighbourhood as original_neighbourhood,
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, p.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,
similarity(COALESCE(p.name_ar, ''), $1) + similarity(COALESCE(p.name, ''), $1) + similarity(COALESCE(p.neighbourhood, ''), $1) as relevance
FROM ${tableName} p
LEFT JOIN admin_boundaries n ON p.admin_level10_id = n.id
LEFT JOIN admin_boundaries d ON p.admin_level8_id = d.id
LEFT JOIN admin_boundaries g ON p.admin_level4_id = g.id
WHERE (p.name_ar % $1 OR p.name % $1 OR p.neighbourhood % $1 OR p.name_ar ILIKE $4 OR p.name ILIKE $4 OR p.neighbourhood ILIKE $4)
${hasLocation ? `AND (p.location && ST_Expand(ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326), $5::float) OR ST_DistanceSphere(p.location, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) <= $6::float)` : ''}
ORDER BY relevance DESC, distance ASC LIMIT 15
`;
const results = await repo.query(userQuery, [cleanQuery, lat || null, lon || null, ILikeQuery, radiusInDegrees, radius]);
@@ -98,9 +107,48 @@ export class GeocodingService {
const osmResults = await this.osmPointsRepository.query(osmQuery, [cleanQuery, lat || null, lon || null, ILikeQuery, radiusInDegrees, radius]);
allResults.push(...osmResults);
// --- Consolidation: Overture Maps Integration (Now in primary DB) ---
const overtureQuery = `
(SELECT id::text,
COALESCE(names->>'primary', names->>'common', 'Building') as name,
COALESCE(names->>'primary', names->>'common', '') as name_ar,
NULL as name_en,
'building' as category,
ST_Y(ST_Centroid(location)) as latitude,
ST_X(ST_Centroid(location)) as longitude,
'' as address,
'overture_buildings' 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,
0.6 as relevance
FROM overture_building
WHERE (names->>'primary' ILIKE $4 OR names->>'common' ILIKE $4)
LIMIT 15)
UNION ALL
(SELECT id::text,
COALESCE(names->>'primary', names->>'common', 'Street') as name,
COALESCE(names->>'primary', names->>'common', '') as name_ar,
NULL as name_en,
'transportation' as category,
ST_Y(ST_Centroid(location)) as latitude,
ST_X(ST_Centroid(location)) as longitude,
'' as address,
'overture_streets' 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,
0.6 as relevance
FROM overture_segment
WHERE (names->>'primary' ILIKE $4 OR names->>'common' ILIKE $4)
LIMIT 15)
`;
try {
const overtureResults = await this.osmPointsRepository.query(overtureQuery, [cleanQuery, lat || null, lon || null, ILikeQuery]);
allResults.push(...overtureResults);
} catch (err) {
this.logger.warn('Overture tables search failed, logging error.', err);
}
const sortedResults = allResults
.sort((a, b) => hasLocation ? ((a.distance - b.distance) || (b.relevance - a.relevance)) : ((b.relevance - a.relevance) || (a.distance - b.distance)))
.slice(0, 20)
.slice(0, 25)
.map(r => ({
...r,
latitude: parseFloat(r.latitude),
@@ -111,7 +159,7 @@ export class GeocodingService {
return { results: sortedResults };
} catch (e) {
this.logger.error('Optimized Spatial Search failed:', e);
this.logger.error('Search failed:', e);
return { results: [] };
}
}
@@ -121,10 +169,19 @@ export class GeocodingService {
const repo = this.getRepositoryForCoords(lat, lng);
const tableName = this.getTableNameForRepo(repo);
const query = `
SELECT id, name, name_ar, category, latitude, longitude, address, 'user_place' as source,
ST_DistanceSphere(location::geometry, ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326)) as distance
FROM ${tableName} WHERE location IS NOT NULL
ORDER BY location::geometry <-> ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) ASC LIMIT 3
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 admin_boundaries n ON p.admin_level10_id = n.id
LEFT JOIN admin_boundaries d ON p.admin_level8_id = d.id
LEFT JOIN admin_boundaries g ON p.admin_level4_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]);
} catch (error) {
@@ -173,11 +230,23 @@ export class GeocodingService {
async getAllPlacesGeoJSON() {
try {
const q = `SELECT id, name_ar as name, category, latitude, longitude, address, 'syria' as region FROM places_syria UNION ALL SELECT id, name_ar as name, category, latitude, longitude, address, 'jordan' as region FROM places_jordan UNION ALL SELECT id, name_ar as name, category, latitude, longitude, address, 'egypt' as region FROM places_egypt`;
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 }}));
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: [] }; }
} catch (e) {
return { type: 'FeatureCollection', features: [] };
}
}
async deletePlacesByName(name: string, country: string) {