1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import { round } from "https://esm.town/v/mxdvl/round";
import { sqlite } from "https://esm.town/v/std/sqlite";
import { array, number, object, optional, parse, string } from "npm:valibot";
await sqlite.execute(`create table if not exists cities(
key text unique,
value text
)`);
function two_decimals(number: number) {
return round(number, 2);
}
const schema = object({
results: array(object({
components: object({
_normalized_city: optional(string()),
}),
})),
});
async function add_city(coordinates: Coordinates): Promise<string> {
const searchParams = new URLSearchParams({
q: [
coordinates[1],
coordinates[0],
].map(two_decimals).join(","),
language: "native",
key: Deno.env.get("OPENCAGE_API_KEY"),
});
const { results: [first] } = await fetch(`https://api.opencagedata.com/geocode/v1/json?${searchParams}`)
.then(response => response.json())
.then(json => parse(schema, json))
.catch(() => ({ results: [] }));
const city = first?.components?._normalized_city;
if (city === undefined) return;
const key = coordinates.map(two_decimals).join(",");
await sqlite.execute({
sql: `insert into cities(key, value) values (:key, :value)`,
args: { key, value: city },
});
return city;
}
type Coordinates = readonly [number, number];
export async function city(coordinates: Coordinates): Promise<string> {
const key = coordinates.map(two_decimals).join(",");
const query = await sqlite.execute({
sql: `select key, value from cities where key = :key`,
args: { key },
});
const city = query.rows[0]?.[1] ?? add_city(coordinates);
return city;
}
export default async function(request: Request): Promise<Response> {
const searchParams = new URL(request.url).searchParams;
if (searchParams.get("list")) {
const query = await sqlite.execute(`select key, value from cities`);
return Response.json(query.rows);
}
const latitude = Number(searchParams.get("latitude"));
const longitude = Number(searchParams.get("longitude"));
if (latitude === 0 && longitude === 0) return Response.json({ error: "Invalid coordinates", latitude, longitude });
return Response.json({
name: await city([longitude, latitude]),
latitude,
longitude,
});
}