Temporary implement pixel to region feature
This commit is contained in:
+23
-6
@@ -88,12 +88,16 @@ model FavoriteLocation {
|
||||
}
|
||||
|
||||
model Tile {
|
||||
id Int @id @default(autoincrement())
|
||||
season Int @default(0)
|
||||
id Int @id @default(autoincrement())
|
||||
season Int @default(0)
|
||||
x Int
|
||||
y Int
|
||||
imageData Bytes?
|
||||
pixels Pixel[]
|
||||
regionId Int?
|
||||
region Region? @relation(fields: [regionId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt @default(now())
|
||||
|
||||
@@unique([season, x, y])
|
||||
}
|
||||
@@ -115,11 +119,15 @@ model Pixel {
|
||||
}
|
||||
|
||||
model Region {
|
||||
id Int @id @default(autoincrement())
|
||||
cityId Int @unique
|
||||
name String
|
||||
id Int @id @default(autoincrement())
|
||||
cityId Int
|
||||
city City @relation(fields: [cityId], references: [id])
|
||||
number Int
|
||||
countryId Int
|
||||
tiles Tile[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt @default(now())
|
||||
|
||||
@@unique([cityId, number])
|
||||
}
|
||||
|
||||
model ProfilePicture {
|
||||
@@ -174,3 +182,12 @@ model SiteContent {
|
||||
|
||||
@@index([key, locale])
|
||||
}
|
||||
|
||||
model City {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
countryId Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt @default(now())
|
||||
regions Region[]
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
export interface Region {
|
||||
id: number;
|
||||
cityId: number;
|
||||
name: string;
|
||||
number: number;
|
||||
countryId: number;
|
||||
flagId: number;
|
||||
}
|
||||
|
||||
export function getRegionForCoordinates(_tileX: number, _tileY: number, _x: number, _y: number): Region | null {
|
||||
// TODO: implement region lookup using coordinate data
|
||||
// After running the scraper (scripts/scrape_regions.py) and importing the data,
|
||||
// you can implement a proper lookup using the Region table or a spatial index.
|
||||
//
|
||||
// const globalX = tileX * 1000 + x;
|
||||
// const globalY = tileY * 1000 + y;
|
||||
//
|
||||
// For now, return null to avoid showing incorrect region data.
|
||||
// See scripts/README.md for instructions on scraping and importing region data.
|
||||
return null;
|
||||
}
|
||||
+15
-2
@@ -4,6 +4,8 @@ import { checkColorUnlocked, COLOR_PALETTE } from "../utils/colors.js";
|
||||
import { calculateChargeRecharge } from "../utils/charges.js";
|
||||
import { getRegionForCoordinates } from "../config/regions.js";
|
||||
import { calculateLevel, calculateMaxChargesForLevel } from "../utils/levels.js";
|
||||
import { LEVEL_BASE_PIXEL, LEVEL_EXPONENT, LEVEL_UP_DROPLETS_REWARD, LEVEL_UP_MAX_CHARGES_REWARD, PAINTED_DROPLETS_REWARD } from "../config/pixel.js";
|
||||
import { RegionService } from "./region.js";
|
||||
|
||||
export interface PaintPixelsInput {
|
||||
tileX: number;
|
||||
@@ -36,6 +38,17 @@ export interface PixelInfoResult {
|
||||
export class PixelService {
|
||||
constructor(private prisma: PrismaClient) {}
|
||||
|
||||
private regionService: RegionService;
|
||||
|
||||
constructor(private prisma: PrismaClient) {
|
||||
const canvas = createCanvas(1, 1);
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.clearRect(0, 0, 1, 1);
|
||||
this.emptyTile = canvas.toBuffer("image/png");
|
||||
|
||||
this.regionService = new RegionService(prisma);
|
||||
}
|
||||
|
||||
async getRandomTile(): Promise<RandomTileResult> {
|
||||
const recentThreshold = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||
|
||||
@@ -128,7 +141,7 @@ export class PixelService {
|
||||
};
|
||||
}
|
||||
|
||||
const region = getRegionForCoordinates(tileX, tileY, x, y);
|
||||
const region = await this.regionService.getRegionForCoordinates(tileX, tileY, season);
|
||||
|
||||
return { paintedBy, region };
|
||||
}
|
||||
@@ -267,7 +280,7 @@ export class PixelService {
|
||||
if (regionCache.has(coordKey)) {
|
||||
region = regionCache.get(coordKey);
|
||||
} else {
|
||||
region = getRegionForCoordinates(tileX, tileY, x, y);
|
||||
region = await this.regionService.getRegionForCoordinates(tileX, tileY, season);
|
||||
regionCache.set(coordKey, region);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
import { Prisma, PrismaClient } from "@prisma/client";
|
||||
import {
|
||||
getCountryByCode,
|
||||
getCountryById,
|
||||
getUnknownRegion,
|
||||
pixelsToLatLon,
|
||||
} from "../utils/region.js";
|
||||
|
||||
export interface Region {
|
||||
id: number;
|
||||
cityId: number;
|
||||
name: string;
|
||||
number: number;
|
||||
countryId: number;
|
||||
flagId: number;
|
||||
}
|
||||
|
||||
export class RegionService {
|
||||
constructor(private prisma: PrismaClient) {}
|
||||
|
||||
// Ref: https://nominatim.org/release-docs/develop/api/Reverse/
|
||||
async nominatim(lat: number, lon: number) {
|
||||
const response = await fetch(
|
||||
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lon}&zoom=5&accept-language=en`
|
||||
);
|
||||
const data: any = await response.json();
|
||||
|
||||
let city =
|
||||
data.address.city || data.address.city_district || data.address.state;
|
||||
if (city) {
|
||||
city = city.replace(/Province/i, "").trim();
|
||||
}
|
||||
|
||||
return {
|
||||
city,
|
||||
country: data.address.country,
|
||||
country_code: data.address.country_code,
|
||||
};
|
||||
}
|
||||
|
||||
async resetAllRegionsData() {
|
||||
await this.prisma.tile.updateMany({
|
||||
data: { regionId: null },
|
||||
});
|
||||
await this.prisma.region.deleteMany();
|
||||
await this.prisma.city.deleteMany();
|
||||
}
|
||||
|
||||
async getRegionForCoordinates(
|
||||
tileX: number,
|
||||
tileY: number,
|
||||
season: number = 0
|
||||
): Promise<Region> {
|
||||
// For debugging purposes only
|
||||
// await this.resetAllRegionsData();
|
||||
|
||||
let tile = await this.prisma.tile.findFirst({
|
||||
where: { season, x: tileX, y: tileY },
|
||||
include: { region: { include: { city: true } } },
|
||||
});
|
||||
|
||||
if (!tile) {
|
||||
tile = await this.prisma.tile.create({
|
||||
data: { season, x: tileX, y: tileY },
|
||||
include: { region: { include: { city: true } } },
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
if (!tile.regionId) {
|
||||
const centerX = tile.x * 1000 + 500;
|
||||
const centerY = tile.y * 1000 + 500;
|
||||
const [lat, lon] = pixelsToLatLon(centerX, centerY);
|
||||
|
||||
console.log(`Getting region for coordinates ${lat}, ${lon}.`);
|
||||
const nominatimData = await this.nominatim(lat, lon);
|
||||
if (!nominatimData.city)
|
||||
throw new Error(`City not found for coordinates ${lat}, ${lon}.`);
|
||||
|
||||
const country = getCountryByCode(nominatimData.country_code);
|
||||
if (!country)
|
||||
throw new Error(
|
||||
`Country code ${nominatimData.country_code} not found.`
|
||||
);
|
||||
|
||||
let city = await this.prisma.city.findFirst({
|
||||
where: { name: nominatimData.city },
|
||||
});
|
||||
|
||||
if (!city) {
|
||||
city = await this.prisma.city.create({
|
||||
data: {
|
||||
name: nominatimData.city,
|
||||
countryId: country.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let region = await this.prisma.region.findFirst({
|
||||
where: { cityId: city.id, number: 1 },
|
||||
});
|
||||
|
||||
if (!region) {
|
||||
region = await this.prisma.region.create({
|
||||
data: { cityId: city.id, number: 1 },
|
||||
});
|
||||
}
|
||||
|
||||
tile = await this.prisma.tile.update({
|
||||
where: { id: tile.id },
|
||||
data: { regionId: region.id },
|
||||
include: { region: { include: { city: true } } },
|
||||
});
|
||||
}
|
||||
|
||||
const country = getCountryById(tile.region.city.countryId);
|
||||
if (!country)
|
||||
throw new Error(`Country ID ${tile.region.city.countryId} not found.`);
|
||||
|
||||
return {
|
||||
id: tile.region.id,
|
||||
cityId: tile.region.cityId,
|
||||
number: tile.region.number,
|
||||
name: tile.region.city.name,
|
||||
countryId: country.id,
|
||||
flagId: country.flag,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
const country = getCountryById(242);
|
||||
return {
|
||||
id: country!.id,
|
||||
cityId: 0,
|
||||
number: 0,
|
||||
name: country!.name,
|
||||
countryId: country!.id,
|
||||
flagId: country!.flag,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user