feat: sync source-backed OSM facility entrances

This commit is contained in:
Hamza-Ayed
2026-09-29 15:25:38 +03:00
parent 58310330b0
commit f4e24b0725
17 changed files with 772 additions and 391 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ async function testPoiGates() {
console.log(` -> Navigate to: ${gate.latitude}, ${gate.longitude}`);
});
} else {
console.log(` ⚠️ No gates found. Sero drops will default to centroid.`);
console.log(` ⚠️ No source-backed entrance is available for this place.`);
}
}
console.log('--------------------------------------------------');
@@ -1,4 +1,4 @@
import { Controller, Get, Post, Delete, Body, Query, UseGuards, Req, HttpException, HttpStatus } from '@nestjs/common';
import { Controller, Get, Post, Delete, Body, Query, Param, UseGuards, Req, HttpException, HttpStatus } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiQuery, ApiHeader, ApiExcludeEndpoint } from '@nestjs/swagger';
import { GeocodingService } from './geocoding.service';
import { AdminBoundariesService } from './admin-boundaries.service';
@@ -33,6 +33,12 @@ export class GeocodingController {
return this.geocodingService.searchPlaces(q, lat, lng, radius, country);
}
@Get('places/:placeId/gates')
@ApiOperation({ summary: 'Get source-backed entrances and gates for a place' })
async getPlaceGates(@Param('placeId') placeId: string) {
return this.geocodingService.getPlaceGates(placeId);
}
@Get('autocomplete')
@ApiOperation({ summary: 'Fast autocomplete for live typing' })
@ApiQuery({ name: 'q', required: true })
+42 -2
View File
@@ -172,7 +172,8 @@ export class GeocodingService {
if (placeIds.length > 0) {
try {
const gates = await this.osmPointsRepository.query(
`SELECT place_id, gate_name_ar, gate_name_en, latitude, longitude, is_main_gate
`SELECT place_id, gate_name_ar, gate_name_en, latitude, longitude, is_main_gate,
source, source_id, gate_type, access, source_tags, last_seen_at
FROM place_gates
WHERE place_id = ANY($1)`,
[placeIds]
@@ -185,7 +186,16 @@ export class GeocodingService {
name_en: g.gate_name_en,
latitude: parseFloat(g.latitude),
longitude: parseFloat(g.longitude),
is_main_gate: g.is_main_gate
is_main_gate: g.is_main_gate,
source: g.source,
source_id: g.source_id,
gate_type: g.gate_type,
access: g.access,
source_tags: g.source_tags,
source_url: g.source === 'openstreetmap' && g.source_id
? `https://www.openstreetmap.org/node/${g.source_id}`
: null,
last_seen_at: g.last_seen_at,
}));
if (placeGates.length > 0) {
r.gates = placeGates;
@@ -238,6 +248,36 @@ export class GeocodingService {
}
}
async getPlaceGates(placeId: string) {
const gates = await this.osmPointsRepository.query(
`SELECT place_id, gate_name_ar, gate_name_en, latitude, longitude, is_main_gate,
source, source_id, gate_type, access, source_tags, last_seen_at
FROM place_gates WHERE place_id = $1
ORDER BY is_main_gate DESC, gate_type ASC, id ASC`,
[placeId],
);
return {
place_id: placeId,
attribution: '© OpenStreetMap contributors',
gates: gates.map((gate: any) => ({
name_ar: gate.gate_name_ar,
name_en: gate.gate_name_en,
latitude: Number(gate.latitude),
longitude: Number(gate.longitude),
is_main_gate: gate.is_main_gate,
gate_type: gate.gate_type,
access: gate.access,
source: gate.source,
source_id: gate.source_id,
source_url: gate.source === 'openstreetmap' && gate.source_id
? `https://www.openstreetmap.org/node/${gate.source_id}`
: null,
source_tags: gate.source_tags,
last_seen_at: gate.last_seen_at,
})),
};
}
private formatResults(results: any[], hasLocation: boolean, relativePrefix: string = '') {
const seenStreets = new Set<string>();