100 lines
4.6 KiB
TypeScript
100 lines
4.6 KiB
TypeScript
import { Injectable, OnModuleInit, Logger } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Repository } from 'typeorm';
|
|
import { PlaceSyria } from './entities/place-syria.entity';
|
|
|
|
@Injectable()
|
|
export class GeocodingInitService implements OnModuleInit {
|
|
private readonly logger = new Logger(GeocodingInitService.name);
|
|
|
|
constructor(
|
|
@InjectRepository(PlaceSyria)
|
|
private readonly repo: Repository<PlaceSyria>,
|
|
) {}
|
|
|
|
async onModuleInit() {
|
|
this.logger.log('Checking for PostGIS extensions and triggers...');
|
|
try {
|
|
// Ensure PostGIS and Trigram extensions are enabled
|
|
await this.repo.query('CREATE EXTENSION IF NOT EXISTS postgis;');
|
|
await this.repo.query('CREATE EXTENSION IF NOT EXISTS pg_trgm;');
|
|
|
|
// Migration: Rename old admin_level columns to descriptive names if they exist
|
|
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 $$;
|
|
`);
|
|
}
|
|
}
|
|
|
|
// Cleanup: Create a trigger to automatically update 'location' when lat/lng changes
|
|
await this.repo.query(`
|
|
CREATE OR REPLACE FUNCTION sync_place_location() RETURNS trigger AS $$
|
|
BEGIN
|
|
IF NEW.latitude IS NOT NULL AND NEW.longitude IS NOT NULL THEN
|
|
NEW.location := ST_SetSRID(ST_MakePoint(CAST(NEW.longitude AS FLOAT), CAST(NEW.latitude AS FLOAT)), 4326);
|
|
|
|
-- 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();
|
|
`);
|
|
|
|
// 3. Triggers for Jordan and Egypt
|
|
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();
|
|
`);
|
|
|
|
// 4. GIST Geometry Indexes for Jordan and Egypt
|
|
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);');
|
|
|
|
// 5. GIST Trigram Indexes for Jordan and Egypt
|
|
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);
|
|
}
|
|
}
|
|
}
|