|
1 |
| -// import initSearch, {search} from 'nz-search' |
2 |
| - |
3 |
| -const osmUrl = 'https://nominatim.openstreetmap.org/search' |
4 |
| -const nzPlacesUrl = "https://nz-places.now.sh/api/search"; |
5 |
| - |
6 |
| -export interface GeocodeResult { |
7 |
| - lat: number; |
8 |
| - lon: number; |
9 |
| - name: string; |
10 |
| - type: string; |
11 |
| -} |
12 |
| - |
13 |
| -let initPromise: Promise<typeof import('nz-search')>; |
14 |
| -function getSearch() { |
15 |
| - if (!initPromise) { |
16 |
| - initPromise = (async () => { |
17 |
| - const [places, searchApi] = await Promise.all([(await fetch('data/places.json')).arrayBuffer(), import('nz-search')]) |
18 |
| - await searchApi.default() |
19 |
| - searchApi.load_data(new Uint8Array(places)) |
20 |
| - return searchApi |
21 |
| - })() |
| 1 | +import { getPlaces, type Place } from './places'; |
| 2 | + |
| 3 | +const getMatch = (queryParts: string[], place: Place) => { |
| 4 | + const lowerName = place.name.toLowerCase(); |
| 5 | + let rank = 0 |
| 6 | + let lastMatchIndex: number | undefined = undefined |
| 7 | + |
| 8 | + for (const part of queryParts) { |
| 9 | + const matchIndex = lowerName.indexOf(part) |
| 10 | + if (matchIndex === -1) return 0 |
| 11 | + |
| 12 | + // If this is the first part, rank it higher if its at the start than if its somewhere else. |
| 13 | + if (lastMatchIndex === undefined) { |
| 14 | + rank += (matchIndex === 0 |
| 15 | + ? 1 |
| 16 | + : 1 / matchIndex) |
| 17 | + } else { |
| 18 | + const numerator = matchIndex <= lastMatchIndex ? 0.5 : 1 |
| 19 | + rank += numerator / Math.abs(matchIndex - lastMatchIndex) |
| 20 | + } |
22 | 21 | }
|
23 |
| - return initPromise |
| 22 | + return rank |
24 | 23 | }
|
25 | 24 |
|
26 |
| -const searchOsm = async (query: string): Promise<GeocodeResult[]> => { |
27 |
| - // Hackily include the country because the nominatim API is terrible. |
28 |
| - query = query + ", NZ"; |
29 |
| - const results: GeocodeResult[] = await fetch(`${osmUrl}?q=${encodeURIComponent(query)}&format=jsonv2`) |
30 |
| - .then(r => r.json()); |
| 25 | +const searchNzPlaces = async (query: string, maxResults = 100): Promise<Place[]> => { |
| 26 | + const lowerQuery = query.toLowerCase() |
| 27 | + const lowerQueryParts = lowerQuery.split(' ').filter(p => p) |
31 | 28 |
|
32 |
| - for (const result of results) { |
33 |
| - result['name'] = result['display_name']; |
34 |
| - } |
35 |
| - return results; |
36 |
| -} |
| 29 | + const places = await getPlaces() |
37 | 30 |
|
38 |
| -const searchNzPlaces = async (query: string): Promise<GeocodeResult[]> => { |
39 |
| - const api = await getSearch() |
| 31 | + const results: Place[] = [] |
| 32 | + for (const place of places) { |
| 33 | + const match = getMatch(lowerQueryParts, place) |
| 34 | + if (match === 0) continue |
40 | 35 |
|
41 |
| - const results = api.search(query, 100) |
| 36 | + results.push(place) |
| 37 | + if (results.length >= maxResults) break |
| 38 | + } |
42 | 39 | return results
|
43 | 40 | }
|
44 | 41 |
|
45 |
| -export const findPlace = async (lat: number, lon: number) => { |
46 |
| - const search = await getSearch() |
47 |
| - return search.closest_place(lat, lon, 0.1) |
48 |
| -} |
49 |
| - |
50 |
| -export default async (query: string, sources=[searchNzPlaces]): Promise<GeocodeResult[]> => { |
| 42 | +export default async (query: string, sources = [searchNzPlaces]): Promise<Place[]> => { |
51 | 43 | const results = await Promise.all(sources.map(s => s(query)));
|
52 | 44 | return results.reduce((prev, next) => [...prev, ...next], [])
|
53 | 45 | }
|
0 commit comments