diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 26c0b8d..ad6281f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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[] +} diff --git a/src/config/regions.ts b/src/config/regions.ts deleted file mode 100644 index 75f053c..0000000 --- a/src/config/regions.ts +++ /dev/null @@ -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; -} diff --git a/src/services/pixel.ts b/src/services/pixel.ts index 0d0ed97..d22e145 100644 --- a/src/services/pixel.ts +++ b/src/services/pixel.ts @@ -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 { 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); } diff --git a/src/services/region.ts b/src/services/region.ts new file mode 100644 index 0000000..abfe3d6 --- /dev/null +++ b/src/services/region.ts @@ -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 { + // 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, + }; + } + } +} diff --git a/src/utils/region.ts b/src/utils/region.ts new file mode 100644 index 0000000..9f301c3 --- /dev/null +++ b/src/utils/region.ts @@ -0,0 +1,67 @@ +const initialResolution = 40075.016685578485; +const $o = 20037508.342789244; + +const countries = JSON.parse( + `[{"id":1,"name":"Afghanistan","code":"AF","flag":"๐Ÿ‡ฆ๐Ÿ‡ซ"},{"id":2,"name":"Albania","code":"AL","flag":"๐Ÿ‡ฆ๐Ÿ‡ฑ"},{"id":3,"name":"Algeria","code":"DZ","flag":"๐Ÿ‡ฉ๐Ÿ‡ฟ"},{"id":4,"name":"American Samoa","code":"AS","flag":"๐Ÿ‡ฆ๐Ÿ‡ธ"},{"id":5,"name":"Andorra","code":"AD","flag":"๐Ÿ‡ฆ๐Ÿ‡ฉ"},{"id":6,"name":"Angola","code":"AO","flag":"๐Ÿ‡ฆ๐Ÿ‡ด"},{"id":7,"name":"Anguilla","code":"AI","flag":"๐Ÿ‡ฆ๐Ÿ‡ฎ"},{"id":8,"name":"Antarctica","code":"AQ","flag":"๐Ÿ‡ฆ๐Ÿ‡ถ"},{"id":9,"name":"Antigua and Barbuda","code":"AG","flag":"๐Ÿ‡ฆ๐Ÿ‡ฌ"},{"id":10,"name":"Argentina","code":"AR","flag":"๐Ÿ‡ฆ๐Ÿ‡ท"},{"id":11,"name":"Armenia","code":"AM","flag":"๐Ÿ‡ฆ๐Ÿ‡ฒ"},{"id":12,"name":"Aruba","code":"AW","flag":"๐Ÿ‡ฆ๐Ÿ‡ผ"},{"id":13,"name":"Australia","code":"AU","flag":"๐Ÿ‡ฆ๐Ÿ‡บ"},{"id":14,"name":"Austria","code":"AT","flag":"๐Ÿ‡ฆ๐Ÿ‡น"},{"id":15,"name":"Azerbaijan","code":"AZ","flag":"๐Ÿ‡ฆ๐Ÿ‡ฟ"},{"id":16,"name":"Bahamas","code":"BS","flag":"๐Ÿ‡ง๐Ÿ‡ธ"},{"id":17,"name":"Bahrain","code":"BH","flag":"๐Ÿ‡ง๐Ÿ‡ญ"},{"id":18,"name":"Bangladesh","code":"BD","flag":"๐Ÿ‡ง๐Ÿ‡ฉ"},{"id":19,"name":"Barbados","code":"BB","flag":"๐Ÿ‡ง๐Ÿ‡ง"},{"id":20,"name":"Belarus","code":"BY","flag":"๐Ÿ‡ง๐Ÿ‡พ"},{"id":21,"name":"Belgium","code":"BE","flag":"๐Ÿ‡ง๐Ÿ‡ช"},{"id":22,"name":"Belize","code":"BZ","flag":"๐Ÿ‡ง๐Ÿ‡ฟ"},{"id":23,"name":"Benin","code":"BJ","flag":"๐Ÿ‡ง๐Ÿ‡ฏ"},{"id":24,"name":"Bermuda","code":"BM","flag":"๐Ÿ‡ง๐Ÿ‡ฒ"},{"id":25,"name":"Bhutan","code":"BT","flag":"๐Ÿ‡ง๐Ÿ‡น"},{"id":26,"name":"Bolivia","code":"BO","flag":"๐Ÿ‡ง๐Ÿ‡ด"},{"id":27,"name":"Bonaire","code":"BQ","flag":"๐Ÿ‡ง๐Ÿ‡ถ"},{"id":28,"name":"Bosnia and Herzegovina","code":"BA","flag":"๐Ÿ‡ง๐Ÿ‡ฆ"},{"id":29,"name":"Botswana","code":"BW","flag":"๐Ÿ‡ง๐Ÿ‡ผ"},{"id":30,"name":"Bouvet Island","code":"BV","flag":"๐Ÿ‡ง๐Ÿ‡ป"},{"id":31,"name":"Brazil","code":"BR","flag":"๐Ÿ‡ง๐Ÿ‡ท"},{"id":32,"name":"British Indian Ocean Territory","code":"IO","flag":"๐Ÿ‡ฎ๐Ÿ‡ด"},{"id":33,"name":"Brunei Darussalam","code":"BN","flag":"๐Ÿ‡ง๐Ÿ‡ณ"},{"id":34,"name":"Bulgaria","code":"BG","flag":"๐Ÿ‡ง๐Ÿ‡ฌ"},{"id":35,"name":"Burkina Faso","code":"BF","flag":"๐Ÿ‡ง๐Ÿ‡ซ"},{"id":36,"name":"Burundi","code":"BI","flag":"๐Ÿ‡ง๐Ÿ‡ฎ"},{"id":37,"name":"Cabo Verde","code":"CV","flag":"๐Ÿ‡จ๐Ÿ‡ป"},{"id":38,"name":"Cambodia","code":"KH","flag":"๐Ÿ‡ฐ๐Ÿ‡ญ"},{"id":39,"name":"Cameroon","code":"CM","flag":"๐Ÿ‡จ๐Ÿ‡ฒ"},{"id":40,"name":"Canada","code":"CA","flag":"๐Ÿ‡จ๐Ÿ‡ฆ"},{"id":41,"name":"Cayman Islands","code":"KY","flag":"๐Ÿ‡ฐ๐Ÿ‡พ"},{"id":42,"name":"Central African Republic","code":"CF","flag":"๐Ÿ‡จ๐Ÿ‡ซ"},{"id":43,"name":"Chad","code":"TD","flag":"๐Ÿ‡น๐Ÿ‡ฉ"},{"id":44,"name":"Chile","code":"CL","flag":"๐Ÿ‡จ๐Ÿ‡ฑ"},{"id":45,"name":"China","code":"CN","flag":"๐Ÿ‡จ๐Ÿ‡ณ"},{"id":46,"name":"Christmas Island","code":"CX","flag":"๐Ÿ‡จ๐Ÿ‡ฝ"},{"id":47,"name":"Cocos (Keeling) Islands","code":"CC","flag":"๐Ÿ‡จ๐Ÿ‡จ"},{"id":48,"name":"Colombia","code":"CO","flag":"๐Ÿ‡จ๐Ÿ‡ด"},{"id":49,"name":"Comoros","code":"KM","flag":"๐Ÿ‡ฐ๐Ÿ‡ฒ"},{"id":50,"name":"Congo","code":"CG","flag":"๐Ÿ‡จ๐Ÿ‡ฌ"},{"id":51,"name":"Cook Islands","code":"CK","flag":"๐Ÿ‡จ๐Ÿ‡ฐ"},{"id":52,"name":"Costa Rica","code":"CR","flag":"๐Ÿ‡จ๐Ÿ‡ท"},{"id":53,"name":"Croatia","code":"HR","flag":"๐Ÿ‡ญ๐Ÿ‡ท"},{"id":54,"name":"Cuba","code":"CU","flag":"๐Ÿ‡จ๐Ÿ‡บ"},{"id":55,"name":"Curaรงao","code":"CW","flag":"๐Ÿ‡จ๐Ÿ‡ผ"},{"id":56,"name":"Cyprus","code":"CY","flag":"๐Ÿ‡จ๐Ÿ‡พ"},{"id":57,"name":"Czechia","code":"CZ","flag":"๐Ÿ‡จ๐Ÿ‡ฟ"},{"id":58,"name":"Cรดte d'Ivoire","code":"CI","flag":"๐Ÿ‡จ๐Ÿ‡ฎ"},{"id":59,"name":"Denmark","code":"DK","flag":"๐Ÿ‡ฉ๐Ÿ‡ฐ"},{"id":60,"name":"Djibouti","code":"DJ","flag":"๐Ÿ‡ฉ๐Ÿ‡ฏ"},{"id":61,"name":"Dominica","code":"DM","flag":"๐Ÿ‡ฉ๐Ÿ‡ฒ"},{"id":62,"name":"Dominican Republic","code":"DO","flag":"๐Ÿ‡ฉ๐Ÿ‡ด"},{"id":63,"name":"Ecuador","code":"EC","flag":"๐Ÿ‡ช๐Ÿ‡จ"},{"id":64,"name":"Egypt","code":"EG","flag":"๐Ÿ‡ช๐Ÿ‡ฌ"},{"id":65,"name":"El Salvador","code":"SV","flag":"๐Ÿ‡ธ๐Ÿ‡ป"},{"id":66,"name":"Equatorial Guinea","code":"GQ","flag":"๐Ÿ‡ฌ๐Ÿ‡ถ"},{"id":67,"name":"Eritrea","code":"ER","flag":"๐Ÿ‡ช๐Ÿ‡ท"},{"id":68,"name":"Estonia","code":"EE","flag":"๐Ÿ‡ช๐Ÿ‡ช"},{"id":69,"name":"Eswatini","code":"SZ","flag":"๐Ÿ‡ธ๐Ÿ‡ฟ"},{"id":70,"name":"Ethiopia","code":"ET","flag":"๐Ÿ‡ช๐Ÿ‡น"},{"id":71,"name":"Falkland Islands (Malvinas)","code":"FK","flag":"๐Ÿ‡ซ๐Ÿ‡ฐ"},{"id":72,"name":"Faroe Islands","code":"FO","flag":"๐Ÿ‡ซ๐Ÿ‡ด"},{"id":73,"name":"Fiji","code":"FJ","flag":"๐Ÿ‡ซ๐Ÿ‡ฏ"},{"id":74,"name":"Finland","code":"FI","flag":"๐Ÿ‡ซ๐Ÿ‡ฎ"},{"id":75,"name":"France","code":"FR","flag":"๐Ÿ‡ซ๐Ÿ‡ท"},{"id":76,"name":"French Guiana","code":"GF","flag":"๐Ÿ‡ฌ๐Ÿ‡ซ"},{"id":77,"name":"French Polynesia","code":"PF","flag":"๐Ÿ‡ต๐Ÿ‡ซ"},{"id":78,"name":"French Southern Territories","code":"TF","flag":"๐Ÿ‡น๐Ÿ‡ซ"},{"id":79,"name":"Gabon","code":"GA","flag":"๐Ÿ‡ฌ๐Ÿ‡ฆ"},{"id":80,"name":"Gambia","code":"GM","flag":"๐Ÿ‡ฌ๐Ÿ‡ฒ"},{"id":81,"name":"Georgia","code":"GE","flag":"๐Ÿ‡ฌ๐Ÿ‡ช"},{"id":82,"name":"Germany","code":"DE","flag":"๐Ÿ‡ฉ๐Ÿ‡ช"},{"id":83,"name":"Ghana","code":"GH","flag":"๐Ÿ‡ฌ๐Ÿ‡ญ"},{"id":84,"name":"Gibraltar","code":"GI","flag":"๐Ÿ‡ฌ๐Ÿ‡ฎ"},{"id":85,"name":"Greece","code":"GR","flag":"๐Ÿ‡ฌ๐Ÿ‡ท"},{"id":86,"name":"Greenland","code":"GL","flag":"๐Ÿ‡ฌ๐Ÿ‡ฑ"},{"id":87,"name":"Grenada","code":"GD","flag":"๐Ÿ‡ฌ๐Ÿ‡ฉ"},{"id":88,"name":"Guadeloupe","code":"GP","flag":"๐Ÿ‡ฌ๐Ÿ‡ต"},{"id":89,"name":"Guam","code":"GU","flag":"๐Ÿ‡ฌ๐Ÿ‡บ"},{"id":90,"name":"Guatemala","code":"GT","flag":"๐Ÿ‡ฌ๐Ÿ‡น"},{"id":91,"name":"Guernsey","code":"GG","flag":"๐Ÿ‡ฌ๐Ÿ‡ฌ"},{"id":92,"name":"Guinea","code":"GN","flag":"๐Ÿ‡ฌ๐Ÿ‡ณ"},{"id":93,"name":"Guinea-Bissau","code":"GW","flag":"๐Ÿ‡ฌ๐Ÿ‡ผ"},{"id":94,"name":"Guyana","code":"GY","flag":"๐Ÿ‡ฌ๐Ÿ‡พ"},{"id":95,"name":"Haiti","code":"HT","flag":"๐Ÿ‡ญ๐Ÿ‡น"},{"id":96,"name":"Heard Island and McDonald Islands","code":"HM","flag":"๐Ÿ‡ญ๐Ÿ‡ฒ"},{"id":97,"name":"Honduras","code":"HN","flag":"๐Ÿ‡ญ๐Ÿ‡ณ"},{"id":98,"name":"Hong Kong","code":"HK","flag":"๐Ÿ‡ญ๐Ÿ‡ฐ"},{"id":99,"name":"Hungary","code":"HU","flag":"๐Ÿ‡ญ๐Ÿ‡บ"},{"id":100,"name":"Iceland","code":"IS","flag":"๐Ÿ‡ฎ๐Ÿ‡ธ"},{"id":101,"name":"India","code":"IN","flag":"๐Ÿ‡ฎ๐Ÿ‡ณ"},{"id":102,"name":"Indonesia","code":"ID","flag":"๐Ÿ‡ฎ๐Ÿ‡ฉ"},{"id":103,"name":"Iran","code":"IR","flag":"๐Ÿ‡ฎ๐Ÿ‡ท"},{"id":104,"name":"Iraq","code":"IQ","flag":"๐Ÿ‡ฎ๐Ÿ‡ถ"},{"id":105,"name":"Ireland","code":"IE","flag":"๐Ÿ‡ฎ๐Ÿ‡ช"},{"id":106,"name":"Isle of Man","code":"IM","flag":"๐Ÿ‡ฎ๐Ÿ‡ฒ"},{"id":107,"name":"Israel","code":"IL","flag":"๐Ÿ‡ฎ๐Ÿ‡ฑ"},{"id":108,"name":"Italy","code":"IT","flag":"๐Ÿ‡ฎ๐Ÿ‡น"},{"id":109,"name":"Jamaica","code":"JM","flag":"๐Ÿ‡ฏ๐Ÿ‡ฒ"},{"id":110,"name":"Japan","code":"JP","flag":"๐Ÿ‡ฏ๐Ÿ‡ต"},{"id":111,"name":"Jersey","code":"JE","flag":"๐Ÿ‡ฏ๐Ÿ‡ช"},{"id":112,"name":"Jordan","code":"JO","flag":"๐Ÿ‡ฏ๐Ÿ‡ด"},{"id":113,"name":"Kazakhstan","code":"KZ","flag":"๐Ÿ‡ฐ๐Ÿ‡ฟ"},{"id":114,"name":"Kenya","code":"KE","flag":"๐Ÿ‡ฐ๐Ÿ‡ช"},{"id":115,"name":"Kiribati","code":"KI","flag":"๐Ÿ‡ฐ๐Ÿ‡ฎ"},{"id":116,"name":"Kosovo","code":"XK","flag":"๐Ÿ‡ฝ๐Ÿ‡ฐ"},{"id":117,"name":"Kuwait","code":"KW","flag":"๐Ÿ‡ฐ๐Ÿ‡ผ"},{"id":118,"name":"Kyrgyzstan","code":"KG","flag":"๐Ÿ‡ฐ๐Ÿ‡ฌ"},{"id":119,"name":"Laos","code":"LA","flag":"๐Ÿ‡ฑ๐Ÿ‡ฆ"},{"id":120,"name":"Latvia","code":"LV","flag":"๐Ÿ‡ฑ๐Ÿ‡ป"},{"id":121,"name":"Lebanon","code":"LB","flag":"๐Ÿ‡ฑ๐Ÿ‡ง"},{"id":122,"name":"Lesotho","code":"LS","flag":"๐Ÿ‡ฑ๐Ÿ‡ธ"},{"id":123,"name":"Liberia","code":"LR","flag":"๐Ÿ‡ฑ๐Ÿ‡ท"},{"id":124,"name":"Libya","code":"LY","flag":"๐Ÿ‡ฑ๐Ÿ‡พ"},{"id":125,"name":"Liechtenstein","code":"LI","flag":"๐Ÿ‡ฑ๐Ÿ‡ฎ"},{"id":126,"name":"Lithuania","code":"LT","flag":"๐Ÿ‡ฑ๐Ÿ‡น"},{"id":127,"name":"Luxembourg","code":"LU","flag":"๐Ÿ‡ฑ๐Ÿ‡บ"},{"id":128,"name":"Macao","code":"MO","flag":"๐Ÿ‡ฒ๐Ÿ‡ด"},{"id":129,"name":"Madagascar","code":"MG","flag":"๐Ÿ‡ฒ๐Ÿ‡ฌ"},{"id":130,"name":"Malawi","code":"MW","flag":"๐Ÿ‡ฒ๐Ÿ‡ผ"},{"id":131,"name":"Malaysia","code":"MY","flag":"๐Ÿ‡ฒ๐Ÿ‡พ"},{"id":132,"name":"Maldives","code":"MV","flag":"๐Ÿ‡ฒ๐Ÿ‡ป"},{"id":133,"name":"Mali","code":"ML","flag":"๐Ÿ‡ฒ๐Ÿ‡ฑ"},{"id":134,"name":"Malta","code":"MT","flag":"๐Ÿ‡ฒ๐Ÿ‡น"},{"id":135,"name":"Marshall Islands","code":"MH","flag":"๐Ÿ‡ฒ๐Ÿ‡ญ"},{"id":136,"name":"Martinique","code":"MQ","flag":"๐Ÿ‡ฒ๐Ÿ‡ถ"},{"id":137,"name":"Mauritania","code":"MR","flag":"๐Ÿ‡ฒ๐Ÿ‡ท"},{"id":138,"name":"Mauritius","code":"MU","flag":"๐Ÿ‡ฒ๐Ÿ‡บ"},{"id":139,"name":"Mayotte","code":"YT","flag":"๐Ÿ‡พ๐Ÿ‡น"},{"id":140,"name":"Mexico","code":"MX","flag":"๐Ÿ‡ฒ๐Ÿ‡ฝ"},{"id":141,"name":"Micronesia","code":"FM","flag":"๐Ÿ‡ซ๐Ÿ‡ฒ"},{"id":142,"name":"Moldova","code":"MD","flag":"๐Ÿ‡ฒ๐Ÿ‡ฉ"},{"id":143,"name":"Monaco","code":"MC","flag":"๐Ÿ‡ฒ๐Ÿ‡จ"},{"id":144,"name":"Mongolia","code":"MN","flag":"๐Ÿ‡ฒ๐Ÿ‡ณ"},{"id":145,"name":"Montenegro","code":"ME","flag":"๐Ÿ‡ฒ๐Ÿ‡ช"},{"id":146,"name":"Montserrat","code":"MS","flag":"๐Ÿ‡ฒ๐Ÿ‡ธ"},{"id":147,"name":"Morocco","code":"MA","flag":"๐Ÿ‡ฒ๐Ÿ‡ฆ"},{"id":148,"name":"Mozambique","code":"MZ","flag":"๐Ÿ‡ฒ๐Ÿ‡ฟ"},{"id":149,"name":"Myanmar","code":"MM","flag":"๐Ÿ‡ฒ๐Ÿ‡ฒ"},{"id":150,"name":"Namibia","code":"NA","flag":"๐Ÿ‡ณ๐Ÿ‡ฆ"},{"id":151,"name":"Nauru","code":"NR","flag":"๐Ÿ‡ณ๐Ÿ‡ท"},{"id":152,"name":"Nepal","code":"NP","flag":"๐Ÿ‡ณ๐Ÿ‡ต"},{"id":153,"name":"Netherlands","code":"NL","flag":"๐Ÿ‡ณ๐Ÿ‡ฑ"},{"id":154,"name":"New Caledonia","code":"NC","flag":"๐Ÿ‡ณ๐Ÿ‡จ"},{"id":155,"name":"New Zealand","code":"NZ","flag":"๐Ÿ‡ณ๐Ÿ‡ฟ"},{"id":156,"name":"Nicaragua","code":"NI","flag":"๐Ÿ‡ณ๐Ÿ‡ฎ"},{"id":157,"name":"Niger","code":"NE","flag":"๐Ÿ‡ณ๐Ÿ‡ช"},{"id":158,"name":"Nigeria","code":"NG","flag":"๐Ÿ‡ณ๐Ÿ‡ฌ"},{"id":159,"name":"Niue","code":"NU","flag":"๐Ÿ‡ณ๐Ÿ‡บ"},{"id":160,"name":"Norfolk Island","code":"NF","flag":"๐Ÿ‡ณ๐Ÿ‡ซ"},{"id":161,"name":"North Korea","code":"KP","flag":"๐Ÿ‡ฐ๐Ÿ‡ต"},{"id":162,"name":"North Macedonia","code":"MK","flag":"๐Ÿ‡ฒ๐Ÿ‡ฐ"},{"id":163,"name":"Northern Mariana Islands","code":"MP","flag":"๐Ÿ‡ฒ๐Ÿ‡ต"},{"id":164,"name":"Norway","code":"NO","flag":"๐Ÿ‡ณ๐Ÿ‡ด"},{"id":165,"name":"Oman","code":"OM","flag":"๐Ÿ‡ด๐Ÿ‡ฒ"},{"id":166,"name":"Pakistan","code":"PK","flag":"๐Ÿ‡ต๐Ÿ‡ฐ"},{"id":167,"name":"Palau","code":"PW","flag":"๐Ÿ‡ต๐Ÿ‡ผ"},{"id":168,"name":"Palestine","code":"PS","flag":"๐Ÿ‡ต๐Ÿ‡ธ"},{"id":169,"name":"Panama","code":"PA","flag":"๐Ÿ‡ต๐Ÿ‡ฆ"},{"id":170,"name":"Papua New Guinea","code":"PG","flag":"๐Ÿ‡ต๐Ÿ‡ฌ"},{"id":171,"name":"Paraguay","code":"PY","flag":"๐Ÿ‡ต๐Ÿ‡พ"},{"id":172,"name":"Peru","code":"PE","flag":"๐Ÿ‡ต๐Ÿ‡ช"},{"id":173,"name":"Philippines","code":"PH","flag":"๐Ÿ‡ต๐Ÿ‡ญ"},{"id":174,"name":"Pitcairn","code":"PN","flag":"๐Ÿ‡ต๐Ÿ‡ณ"},{"id":175,"name":"Poland","code":"PL","flag":"๐Ÿ‡ต๐Ÿ‡ฑ"},{"id":176,"name":"Portugal","code":"PT","flag":"๐Ÿ‡ต๐Ÿ‡น"},{"id":177,"name":"Puerto Rico","code":"PR","flag":"๐Ÿ‡ต๐Ÿ‡ท"},{"id":178,"name":"Qatar","code":"QA","flag":"๐Ÿ‡ถ๐Ÿ‡ฆ"},{"id":179,"name":"Republic of the Congo","code":"CD","flag":"๐Ÿ‡จ๐Ÿ‡ฉ"},{"id":180,"name":"Romania","code":"RO","flag":"๐Ÿ‡ท๐Ÿ‡ด"},{"id":181,"name":"Russia","code":"RU","flag":"๐Ÿ‡ท๐Ÿ‡บ"},{"id":182,"name":"Rwanda","code":"RW","flag":"๐Ÿ‡ท๐Ÿ‡ผ"},{"id":183,"name":"Rรฉunion","code":"RE","flag":"๐Ÿ‡ท๐Ÿ‡ช"},{"id":184,"name":"Saint Barthรฉlemy","code":"BL","flag":"๐Ÿ‡ง๐Ÿ‡ฑ"},{"id":185,"name":"Saint Helena","code":"SH","flag":"๐Ÿ‡ธ๐Ÿ‡ญ"},{"id":186,"name":"Saint Kitts and Nevis","code":"KN","flag":"๐Ÿ‡ฐ๐Ÿ‡ณ"},{"id":187,"name":"Saint Lucia","code":"LC","flag":"๐Ÿ‡ฑ๐Ÿ‡จ"},{"id":188,"name":"Saint Martin (French part)","code":"MF","flag":"๐Ÿ‡ฒ๐Ÿ‡ซ"},{"id":189,"name":"Saint Pierre and Miquelon","code":"PM","flag":"๐Ÿ‡ต๐Ÿ‡ฒ"},{"id":190,"name":"Saint Vincent and the Grenadines","code":"VC","flag":"๐Ÿ‡ป๐Ÿ‡จ"},{"id":191,"name":"Samoa","code":"WS","flag":"๐Ÿ‡ผ๐Ÿ‡ธ"},{"id":192,"name":"San Marino","code":"SM","flag":"๐Ÿ‡ธ๐Ÿ‡ฒ"},{"id":193,"name":"Sao Tome and Principe","code":"ST","flag":"๐Ÿ‡ธ๐Ÿ‡น"},{"id":194,"name":"Saudi Arabia","code":"SA","flag":"๐Ÿ‡ธ๐Ÿ‡ฆ"},{"id":195,"name":"Senegal","code":"SN","flag":"๐Ÿ‡ธ๐Ÿ‡ณ"},{"id":196,"name":"Serbia","code":"RS","flag":"๐Ÿ‡ท๐Ÿ‡ธ"},{"id":197,"name":"Seychelles","code":"SC","flag":"๐Ÿ‡ธ๐Ÿ‡จ"},{"id":198,"name":"Sierra Leone","code":"SL","flag":"๐Ÿ‡ธ๐Ÿ‡ฑ"},{"id":199,"name":"Singapore","code":"SG","flag":"๐Ÿ‡ธ๐Ÿ‡ฌ"},{"id":200,"name":"Sint Maarten (Dutch part)","code":"SX","flag":"๐Ÿ‡ธ๐Ÿ‡ฝ"},{"id":201,"name":"Slovakia","code":"SK","flag":"๐Ÿ‡ธ๐Ÿ‡ฐ"},{"id":202,"name":"Slovenia","code":"SI","flag":"๐Ÿ‡ธ๐Ÿ‡ฎ"},{"id":203,"name":"Solomon Islands","code":"SB","flag":"๐Ÿ‡ธ๐Ÿ‡ง"},{"id":204,"name":"Somalia","code":"SO","flag":"๐Ÿ‡ธ๐Ÿ‡ด"},{"id":205,"name":"South Africa","code":"ZA","flag":"๐Ÿ‡ฟ๐Ÿ‡ฆ"},{"id":206,"name":"South Georgia and the South Sandwich Islands","code":"GS","flag":"๐Ÿ‡ฌ๐Ÿ‡ธ"},{"id":207,"name":"South Korea","code":"KR","flag":"๐Ÿ‡ฐ๐Ÿ‡ท"},{"id":208,"name":"South Sudan","code":"SS","flag":"๐Ÿ‡ธ๐Ÿ‡ธ"},{"id":209,"name":"Spain","code":"ES","flag":"๐Ÿ‡ช๐Ÿ‡ธ"},{"id":210,"name":"Sri Lanka","code":"LK","flag":"๐Ÿ‡ฑ๐Ÿ‡ฐ"},{"id":211,"name":"Sudan","code":"SD","flag":"๐Ÿ‡ธ๐Ÿ‡ฉ"},{"id":212,"name":"Suriname","code":"SR","flag":"๐Ÿ‡ธ๐Ÿ‡ท"},{"id":213,"name":"Svalbard and Jan Mayen","code":"SJ","flag":"๐Ÿ‡ธ๐Ÿ‡ฏ"},{"id":214,"name":"Sweden","code":"SE","flag":"๐Ÿ‡ธ๐Ÿ‡ช"},{"id":215,"name":"Switzerland","code":"CH","flag":"๐Ÿ‡จ๐Ÿ‡ญ"},{"id":216,"name":"Syrian Arab Republic","code":"SY","flag":"๐Ÿ‡ธ๐Ÿ‡พ"},{"id":217,"name":"Taiwan","code":"TW","flag":"๐Ÿ‡น๐Ÿ‡ผ"},{"id":218,"name":"Tajikistan","code":"TJ","flag":"๐Ÿ‡น๐Ÿ‡ฏ"},{"id":219,"name":"Tanzania","code":"TZ","flag":"๐Ÿ‡น๐Ÿ‡ฟ"},{"id":220,"name":"Thailand","code":"TH","flag":"๐Ÿ‡น๐Ÿ‡ญ"},{"id":221,"name":"Timor-Leste","code":"TL","flag":"๐Ÿ‡น๐Ÿ‡ฑ"},{"id":222,"name":"Togo","code":"TG","flag":"๐Ÿ‡น๐Ÿ‡ฌ"},{"id":223,"name":"Tokelau","code":"TK","flag":"๐Ÿ‡น๐Ÿ‡ฐ"},{"id":224,"name":"Tonga","code":"TO","flag":"๐Ÿ‡น๐Ÿ‡ด"},{"id":225,"name":"Trinidad and Tobago","code":"TT","flag":"๐Ÿ‡น๐Ÿ‡น"},{"id":226,"name":"Tunisia","code":"TN","flag":"๐Ÿ‡น๐Ÿ‡ณ"},{"id":227,"name":"Turkmenistan","code":"TM","flag":"๐Ÿ‡น๐Ÿ‡ฒ"},{"id":228,"name":"Turks and Caicos Islands","code":"TC","flag":"๐Ÿ‡น๐Ÿ‡จ"},{"id":229,"name":"Tuvalu","code":"TV","flag":"๐Ÿ‡น๐Ÿ‡ป"},{"id":230,"name":"Tรผrkiye","code":"TR","flag":"๐Ÿ‡น๐Ÿ‡ท"},{"id":231,"name":"Uganda","code":"UG","flag":"๐Ÿ‡บ๐Ÿ‡ฌ"},{"id":232,"name":"Ukraine","code":"UA","flag":"๐Ÿ‡บ๐Ÿ‡ฆ"},{"id":233,"name":"United Arab Emirates","code":"AE","flag":"๐Ÿ‡ฆ๐Ÿ‡ช"},{"id":234,"name":"United Kingdom","code":"GB","flag":"๐Ÿ‡ฌ๐Ÿ‡ง"},{"id":235,"name":"United States","code":"US","flag":"๐Ÿ‡บ๐Ÿ‡ธ"},{"id":236,"name":"United States Minor Outlying Islands","code":"UM","flag":"๐Ÿ‡บ๐Ÿ‡ฒ"},{"id":237,"name":"Uruguay","code":"UY","flag":"๐Ÿ‡บ๐Ÿ‡พ"},{"id":238,"name":"Uzbekistan","code":"UZ","flag":"๐Ÿ‡บ๐Ÿ‡ฟ"},{"id":239,"name":"Vanuatu","code":"VU","flag":"๐Ÿ‡ป๐Ÿ‡บ"},{"id":240,"name":"Vatican City","code":"VA","flag":"๐Ÿ‡ป๐Ÿ‡ฆ"},{"id":241,"name":"Venezuela","code":"VE","flag":"๐Ÿ‡ป๐Ÿ‡ช"},{"id":242,"name":"Viet Nam","code":"VN","flag":"๐Ÿ‡ป๐Ÿ‡ณ"},{"id":243,"name":"Virgin Islands","code":"VG","flag":"๐Ÿ‡ป๐Ÿ‡ฌ"},{"id":244,"name":"Virgin Islands","code":"VI","flag":"๐Ÿ‡ป๐Ÿ‡ฎ"},{"id":245,"name":"Wallis and Futuna","code":"WF","flag":"๐Ÿ‡ผ๐Ÿ‡ซ"},{"id":246,"name":"Western Sahara","code":"EH","flag":"๐Ÿ‡ช๐Ÿ‡ญ"},{"id":247,"name":"Yemen","code":"YE","flag":"๐Ÿ‡พ๐Ÿ‡ช"},{"id":248,"name":"Zambia","code":"ZM","flag":"๐Ÿ‡ฟ๐Ÿ‡ฒ"},{"id":249,"name":"Zimbabwe","code":"ZW","flag":"๐Ÿ‡ฟ๐Ÿ‡ผ"},{"id":250,"name":"ร…land Islands","code":"AX","flag":"๐Ÿ‡ฆ๐Ÿ‡ฝ"},{"id":251,"name":"Canary Islands","code":"IC","flag":"๐Ÿ‡ฎ๐Ÿ‡จ"}]` +); + +const resolution = (zoom: number = 11): number => { + return initialResolution / 2 ** zoom; +}; + +const pixelsToMeters = ( + x: number, + y: number, + zoom: number = 11 +): [number, number] => { + const r = resolution(zoom); + const mx = x * r - $o; + const my = $o - y * r; + return [mx, my]; +}; + +const metersToLatLon = (mx: number, my: number): [number, number] => { + const lon = (mx / $o) * 180; + + let lat = (my / $o) * 180; + lat = + (180 / Math.PI) * + (2 * Math.atan(Math.exp((lat * Math.PI) / 180)) - Math.PI / 2); + + return [lat, lon]; +}; + +export const pixelsToLatLon = ( + x: number, + y: number, + zoom: number = 11 +): [number, number] => { + const [mx, my] = pixelsToMeters(x, y, zoom); + return metersToLatLon(mx, my); +}; + +export const getCountryByCode = (code: string) => { + code = code.toUpperCase(); + + const country = countries.find((x: any) => x.code === code); + if (!country) return null; + + return { + id: country.id, + name: country.name, + code: country.code, + flag: country.flag, + }; +}; + +export const getCountryById = (id: number) => { + const country = countries.find((x: any) => x.id === id); + if (!country) return null; + + return { + id: country.id, + name: country.name, + code: country.code, + flag: country.flag, + }; +};