Multi Geocode (#109)

* multi-geocode for new map

* backward compatibility w/ city polygon
This commit is contained in:
Will Freeman
2026-04-19 15:28:05 -06:00
committed by GitHub
parent 2be27e551a
commit 64ac4c227e
4 changed files with 41 additions and 12 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ bun install
### Run locally ### Run locally
```sh ```sh
bun server.ts bun dev
``` ```
## Deployment ## Deployment
+1
View File
@@ -4,6 +4,7 @@
"main": "server.ts", "main": "server.ts",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "NODE_ENV=development bun server.ts",
"start": "bun server.ts" "start": "bun server.ts"
}, },
"dependencies": { "dependencies": {
+32 -5
View File
@@ -34,11 +34,12 @@ const start = async () => {
}); });
}); });
// Coors Light Config // Coors Banquet Config
await server.register(cors, { await server.register(cors, {
origin: (origin, cb) => { origin: (origin, cb) => {
const allowedOrigins = [ const allowedOrigins = [
'http://localhost:5173', // vite dev server 'http://localhost:5173', // DeFlock Legacy
'http://localhost:3000', // FlockHopper
'https://deflock.org', 'https://deflock.org',
'https://www.deflock.org' 'https://www.deflock.org'
]; ];
@@ -80,11 +81,35 @@ const start = async () => {
}, },
}, async (request, reply) => { }, async (request, reply) => {
const { query } = request.query as { query: string }; const { query } = request.query as { query: string };
reply.header('Cache-Control', 'public, max-age=300, s-maxage=86400'); reply.header('Cache-Control', 'public, max-age=86400, s-maxage=86400');
const result = await nominatim.geocodeSingleResult(query); const result = await nominatim.geocodeSingleResult(query);
return result; return result;
}); });
server.get('/geocode/multi', {
schema: {
querystring: {
type: 'object',
properties: {
query: { type: 'string' },
},
required: ['query'],
},
response: {
200: {
type: 'array',
items: NominatimResultSchema,
},
500: { type: 'object', properties: { error: { type: 'string' } } },
},
},
}, async (request, reply) => {
const { query } = request.query as { query: string };
reply.header('Cache-Control', 'public, max-age=86400, s-maxage=86400');
const result = await nominatim.geocodePhrase(query);
return result;
});
server.get('/sponsors/github', { server.get('/sponsors/github', {
schema: { schema: {
querystring: { querystring: {
@@ -110,8 +135,10 @@ const start = async () => {
}); });
try { try {
await server.listen({ host: '0.0.0.0', port: 3000 }); const defaultPort = process.env.NODE_ENV === 'development' ? 3420 : 3000;
console.log('Server listening on port 3000'); const port = process.env.PORT ? parseInt(process.env.PORT, 10) : defaultPort;
await server.listen({ host: '0.0.0.0', port });
console.log(`Server listening on port ${port}`);
} catch (err) { } catch (err) {
console.error('Failed to start server:', err); console.error('Failed to start server:', err);
server.log.error(err); server.log.error(err);
+7 -6
View File
@@ -8,10 +8,10 @@ export const NominatimResultSchema = Type.Object({
boundingbox: Type.Tuple([Type.String(), Type.String(), Type.String(), Type.String()]), boundingbox: Type.Tuple([Type.String(), Type.String(), Type.String(), Type.String()]),
class: Type.String(), class: Type.String(),
display_name: Type.String(), display_name: Type.String(),
geojson: Type.Object({ geojson: Type.Optional(Type.Object({
coordinates: Type.Any(), coordinates: Type.Any(),
type: Type.String(), type: Type.String(),
}), })),
importance: Type.Number(), importance: Type.Number(),
lat: Type.String(), lat: Type.String(),
licence: Type.String(), licence: Type.String(),
@@ -39,16 +39,17 @@ const cache: Cache = createCache({
export class NominatimClient { export class NominatimClient {
baseUrl = 'https://nominatim.openstreetmap.org/search'; baseUrl = 'https://nominatim.openstreetmap.org/search';
async geocodePhrase(query: string): Promise<NominatimResult[]> { async geocodePhrase(query: string, includeGeoJson: boolean = false): Promise<NominatimResult[]> {
const cacheKey = `geocode:${query}`; const cacheKey = `geocode:${query}`;
const cached = await cache.get(cacheKey); const cached = await cache.get(cacheKey);
if (cached) { if (cached) {
return cached as NominatimResult[]; return cached as NominatimResult[];
} }
const url = `${this.baseUrl}?q=${encodeURIComponent(query)}&polygon_geojson=1&format=json`; const geojsonParam = includeGeoJson ? '&polygon_geojson=1' : '';
const url = `${this.baseUrl}?q=${encodeURIComponent(query)}&format=json&addressdetails=1&limit=8&countrycodes=us&dedupe=1${geojsonParam}`;
const response = await fetch(url, { const response = await fetch(url, {
headers: { headers: {
'User-Agent': 'DeFlock/1.1', 'User-Agent': 'DeFlock/1.2',
}, },
}); });
if (!response.ok) { if (!response.ok) {
@@ -60,7 +61,7 @@ export class NominatimClient {
} }
async geocodeSingleResult(query: string): Promise<NominatimResult | null> { async geocodeSingleResult(query: string): Promise<NominatimResult | null> {
const results = await this.geocodePhrase(query); const results = await this.geocodePhrase(query, true);
if (!results.length) return null; if (!results.length) return null;