MIF_E31221222/sigap-website/prisma/seeds/geographic.ts

247 lines
7.4 KiB
TypeScript

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<string, any>;
geometry?: Record<string, any>;
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<void> {
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<string> {
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;
}
}