MENA region data enrichment scripts` -> Wait, let's keep it concise and clean.

*   *Option 3:* `feat: add tourist landmarks support with UI sheets, data models
This commit is contained in:
Hamza-Ayed
2026-09-21 05:21:44 +03:00
parent cff5a8e1ce
commit dc9a736e26
29 changed files with 13396 additions and 1982 deletions
+149
View File
@@ -0,0 +1,149 @@
const fs = require('fs');
const path = require('path');
const { Client } = require(path.join(__dirname, '../../apps/api/node_modules/pg'));
const dataPath = path.join(__dirname, '../../apps/web/public/data/landmarks_mena_expanded.json');
const landmarks = JSON.parse(fs.readFileSync(dataPath, 'utf8'));
const allLandmarks = landmarks;
console.log(`Found ${allLandmarks.length} MENA regional landmarks to ingest.`);
const client = new Client({
host: '188.68.36.205',
port: 5432,
user: 'mapuser',
password: 'mappass',
database: 'mapdb'
});
async function main() {
await client.connect();
console.log('Connected to PostgreSQL mapdb on 188.68.36.205');
await client.query(`CREATE SCHEMA IF NOT EXISTS heritage;`);
await client.query(`
CREATE TABLE IF NOT EXISTS heritage.landmarks (
id SERIAL PRIMARY KEY,
slug VARCHAR(100) UNIQUE NOT NULL,
name_ar VARCHAR(255) NOT NULL,
name_en VARCHAR(255),
category VARCHAR(100) NOT NULL,
era VARCHAR(100),
narrative_ar TEXT,
narrative_en TEXT,
audio_url VARCHAR(500),
image_url VARCHAR(500),
centroid_geom geometry(Point, 4326) NOT NULL,
access_gate_geom geometry(Point, 4326),
parking_geom geometry(Point, 4326),
ticket_office_geom geometry(Point, 4326),
governorate VARCHAR(100),
is_verified BOOLEAN DEFAULT true,
metadata JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_heritage_landmarks_centroid ON heritage.landmarks USING GIST(centroid_geom);
`);
let insertedCount = 0;
let updatedCount = 0;
for (const l of allLandmarks) {
const slug = l.slug || `lm-${l.id}`;
const nameAr = l.name_ar || l.name;
const nameEn = l.name_en || null;
const category = l.category || 'heritage_site';
const era = l.era || null;
const narrativeAr = l.narrative_ar || l.narrative || null;
const narrativeEn = l.narrative_en || null;
const audioUrl = l.audio_url || null;
const imageUrl = l.image_url || l.image || null;
const governorate = l.governorate || null;
const isVerified = l.is_verified ?? true;
const cLng = l.coordinates?.centroid?.lng ?? l.centroid?.lng ?? l.lng ?? (Array.isArray(l.coordinates) ? l.coordinates[0] : null);
const cLat = l.coordinates?.centroid?.lat ?? l.centroid?.lat ?? l.lat ?? (Array.isArray(l.coordinates) ? l.coordinates[1] : null);
const gLng = l.coordinates?.access_gate?.lng ?? l.gate?.lng ?? null;
const gLat = l.coordinates?.access_gate?.lat ?? l.gate?.lat ?? null;
const pLng = l.coordinates?.parking?.lng ?? l.parking?.lng ?? null;
const pLat = l.coordinates?.parking?.lat ?? l.parking?.lat ?? null;
const tLng = l.coordinates?.ticket_office?.lng ?? l.ticket?.lng ?? null;
const tLat = l.coordinates?.ticket_office?.lat ?? l.ticket?.lat ?? null;
const metaObj = {
...(l.metadata || {}),
country: l.country,
curator: l.curator || l.metadata?.curator || 'وزارة السياحة والآثار',
tier: l.tier,
admission: l.admission,
site_polygon: l.site_polygon,
trail_name: l.trail_name || l.metadata?.trail_name,
turathi_trail_id: l.turathi_trail_id || l.metadata?.turathi_trail_id,
turathi_stop_id: l.turathi_stop_id || l.metadata?.turathi_stop_id,
turathi_trail_name: l.turathi_trail_name || l.metadata?.turathi_trail_name,
has_visitor_center: l.has_visitor_center || l.metadata?.has_visitor_center
};
const query = `
INSERT INTO heritage.landmarks (
slug, name_ar, name_en, category, era, narrative_ar, narrative_en,
audio_url, image_url, centroid_geom, access_gate_geom, parking_geom,
ticket_office_geom, governorate, is_verified, metadata, updated_at
) VALUES (
$1, $2, $3, $4, $5, $6, $7,
$8, $9,
ST_SetSRID(ST_Point($10, $11), 4326),
CASE WHEN $12::float IS NOT NULL AND $13::float IS NOT NULL THEN ST_SetSRID(ST_Point($12, $13), 4326) ELSE NULL END,
CASE WHEN $14::float IS NOT NULL AND $15::float IS NOT NULL THEN ST_SetSRID(ST_Point($14, $15), 4326) ELSE NULL END,
CASE WHEN $16::float IS NOT NULL AND $17::float IS NOT NULL THEN ST_SetSRID(ST_Point($16, $17), 4326) ELSE NULL END,
$18, $19, $20::jsonb, CURRENT_TIMESTAMP
)
ON CONFLICT (slug) DO UPDATE SET
name_ar = EXCLUDED.name_ar,
name_en = EXCLUDED.name_en,
category = EXCLUDED.category,
era = EXCLUDED.era,
narrative_ar = EXCLUDED.narrative_ar,
narrative_en = EXCLUDED.narrative_en,
audio_url = EXCLUDED.audio_url,
image_url = EXCLUDED.image_url,
centroid_geom = EXCLUDED.centroid_geom,
access_gate_geom = EXCLUDED.access_gate_geom,
parking_geom = EXCLUDED.parking_geom,
ticket_office_geom = EXCLUDED.ticket_office_geom,
governorate = EXCLUDED.governorate,
is_verified = EXCLUDED.is_verified,
metadata = EXCLUDED.metadata,
updated_at = CURRENT_TIMESTAMP;
`;
const values = [
slug, nameAr, nameEn, category, era, narrativeAr, narrativeEn,
audioUrl, imageUrl,
cLng, cLat,
gLng, gLat,
pLng, pLat,
tLng, tLat,
governorate, isVerified, JSON.stringify(metaObj)
];
await client.query(query, values);
insertedCount++;
}
const finalCount = await client.query('SELECT count(*) as count FROM heritage.landmarks');
console.log(`\n🎉 Ingestion successful!`);
console.log(`Processed: ${insertedCount} landmarks`);
console.log(`Total heritage.landmarks in DB: ${finalCount.rows[0].count}`);
await client.end();
}
main().catch(err => {
console.error('Ingestion error:', err);
process.exit(1);
});