247 lines
7.4 KiB
TypeScript
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;
|
|
}
|
|
} |