import { PrismaClient } from '@prisma/client'; import fs from 'fs'; import * as turf from '@turf/turf'; import { createClient } from '../../app/_utils/supabase/client'; import axios from 'axios'; import xlsx from 'xlsx'; export interface CreateLocationDto { district_id: string; name?: string; description?: string; address?: string; type?: string; latitude: number; longitude: number; land_area?: number; polygon?: Record; geometry?: Record; location?: string; year: number; // Added year field } interface DistrictAreaData { [district: string]: { [year: string]: number; }; } export class GeoJSONSeeder { private mapboxToken: string; private areaData: DistrictAreaData = {}; constructor( private prisma: PrismaClient, private supabase = createClient() ) { this.mapboxToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN || ''; // Load area data from Excel this.loadAreaData(); } private loadAreaData(): void { try { // Load the Excel file const workbook = xlsx.readFile( 'prisma/data/excels/administrations/geographics.xlsx' ); const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; // Convert to JSON const jsonData = xlsx.utils.sheet_to_json(worksheet); // Parse the data into our structure jsonData.forEach((row: any) => { const districtName = row['Kecamatan']; if (districtName) { this.areaData[districtName] = { '2020': this.parseAreaValue(row['2020']), '2021': this.parseAreaValue(row['2021']), '2022': this.parseAreaValue(row['2022']), '2023': this.parseAreaValue(row['2023']), '2024': this.parseAreaValue(row['2024']), }; } }); console.log('Area data loaded successfully'); } catch (error) { console.error('Error loading area data from Excel:', error); } } private parseAreaValue(value: string | number): number { if (typeof value === 'number') { return value; } if (typeof value === 'string') { // Replace comma with dot for decimal return parseFloat(value.replace(',', '.')); } return 0; } private getDistrictArea(districtName: string, year: number): number { // Normalize district name (remove case sensitivity and extra spaces) const normalizedName = districtName.trim().toLowerCase(); // Try to find the district in our data for (const district in this.areaData) { if (district.trim().toLowerCase() === normalizedName) { return this.areaData[district][year.toString()] || 0; } } console.warn( `No area data found for district: ${districtName}, year: ${year}` ); return 0; } async run(): Promise { console.log('Seeding GeoJSON data...'); await this.prisma.units.deleteMany({}); await this.prisma.districts.deleteMany({}); await this.prisma.cities.deleteMany({}); await this.prisma.geographics.deleteMany({}); try { // Load GeoJSON file const regencyGeoJson = JSON.parse( fs.readFileSync('prisma/data/geojson/jember/regency.geojson', 'utf-8') ); const districtGeoJson = JSON.parse( fs.readFileSync('prisma/data/geojson/jember/districts.geojson', 'utf-8') ); // 1. Insert Kota/Kabupaten: Jember let regency; // Declare regency variable outside the loop for (const feature of regencyGeoJson.features) { const properties = feature.properties; const geometry = feature.geometry; // Cleanup code const regencyCode = properties.kode_kk.replace(/\./g, '').trim(); // Insert Regency regency = await this.prisma.cities.create({ data: { id: regencyCode, name: properties.kab_kota, }, }); // 2. Loop Semua District di GeoJSON for (const feature of districtGeoJson.features) { const properties = feature.properties; const geometry = feature.geometry; // Cleanup code const districtCode = properties.kode_kec.replace(/\./g, '').trim(); // Insert District const district = await this.prisma.districts.create({ data: { id: districtCode, name: properties.kecamatan, city_id: regency.id, }, }); console.log(`Inserted district: ${district.name}`); // Buat Location satu kali untuk district (tidak dalam loop) const centroid = turf.centroid(feature); const [longitude, latitude] = centroid.geometry.coordinates; const address = await this.getStreetFromMapbox(longitude, latitude); // Insert locations for each year with appropriate land area const years = [2020, 2021, 2022, 2023, 2024]; for (const year of years) { const landArea = this.getDistrictArea(district.name, year); // Create location data for this district and year const locationData: CreateLocationDto = { district_id: district.id, description: `Location for ${district.name} District in Jember (${year})`, address: address, type: 'district location', year: year, latitude, longitude, land_area: landArea, polygon: geometry, geometry: geometry, location: `POINT(${longitude} ${latitude})`, }; const { error } = await this.supabase .from('geographics') .insert([locationData]) .select(); if (error) { console.error( `Error inserting into locations for district ${district.name} (${year}):`, error ); continue; } console.log( `Inserted geographic data for: ${district.name} (${year}) with area: ${landArea} sq km` ); } } } console.log( 'GeoJSON data seeded successfully!', districtGeoJson.features.length, 'districts inserted with 5 years of area data.' ); } catch (error) { console.error('Error seeding GeoJSON data:', error); } } private async getStreetFromMapbox(lng: number, lat: number): Promise { try { const response = await axios.get( `https://api.mapbox.com/search/geocode/v6/reverse?longitude=${lng}&latitude=${lat}&access_token=${this.mapboxToken}` ); if ( response.data && response.data.features && response.data.features.length > 0 ) { // Extract full_address from the first feature const fullAddress = response.data.features[0].properties.full_address; return ( fullAddress || `Jalan Tidak Diketahui No. ${Math.floor(this.getRandomNumber(1, 100))}` ); } // Fallback if no address found return `Jalan Tidak Diketahui No. ${Math.floor(this.getRandomNumber(1, 100))}`; } catch (error) { console.error('Error fetching street from Mapbox:', error); return `Jalan Tidak Diketahui No. ${Math.floor(this.getRandomNumber(1, 100))}`; } } private getRandomNumber(min: number, max: number): number { return Math.random() * (max - min) + min; } }