Get the pictures right and stop reusing stale answers

Three things you could see on dev1.

Pictures were "roughly right" rather than right, because a coordinate search
only knows what was photographed within 300 m. Wikidata's P18 is one picture
somebody chose to represent that exact object - "Zurletzteninstanz.jpg" for the
restaurant, not a statue down the road - so it now leads, then the Commons
category, and geosearch is the last resort it should always have been. Small
venues carry a wikidata tag far more often than a Commons category, so this is
also what reaches the places the other sources miss.

Descriptions stayed on Wikipedia after the switch to Wikivoyage because the
week-long result cache predates it. It carries a format version now; a release
that changes the shape or the sources invalidates its own entries instead of
serving last week's for another six days.

Ratings and opening hours were being fetched from Google and then dropped on the
floor - the fact list only looked at OpenStreetMap tags. Both are shown now,
whichever provider supplied them.

Also: the column follows a place opened from the map, not only one picked from
search. A POI tap arrives as prefillCoords and never touched the selection, so
the column kept showing the previously added place.

Pictures are a tight three-column grid and only the picture in play is credited
in full, on one line. Crediting all of them at two lines each drowned the
column, and the licence attaches to the one that gets used - whose credit also
stays visible in the inspector after saving.

The request carries the place record the dialog already fetched, so the server
skips a details lookup it was making in parallel with the client's own. That
lookup was measured at 12.8 s for a large OSM relation.
This commit is contained in:
Maurice
2026-08-09 23:48:49 +02:00
committed by Maurice
parent d98d597581
commit f5a483da64
31 changed files with 278 additions and 73 deletions
+1
View File
@@ -97,6 +97,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': 'أفرغ حقل الوصف أولًا',
'places.details.facts': 'معلومات مفيدة',
'places.details.nothing': 'لم يتم العثور على شيء لهذا المكان.',
'places.details.fact.rating': 'التقييم',
'places.details.fact.cuisine': 'المطبخ',
'places.details.fact.openingHours': 'ساعات العمل',
'places.details.fact.menu': 'قائمة الطعام',
+1
View File
@@ -97,6 +97,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': 'Esvazie primeiro o campo de descrição',
'places.details.facts': 'Bom saber',
'places.details.nothing': 'Nada encontrado para este lugar.',
'places.details.fact.rating': 'Avaliação',
'places.details.fact.cuisine': 'Cozinha',
'places.details.fact.openingHours': 'Horário',
'places.details.fact.menu': 'Cardápio',
+1
View File
@@ -95,6 +95,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': 'Buida primer el camp de descripció',
'places.details.facts': 'Bo saber',
'places.details.nothing': "No s'ha trobat res per a aquest lloc.",
'places.details.fact.rating': 'Valoració',
'places.details.fact.cuisine': 'Cuina',
'places.details.fact.openingHours': 'Horari',
'places.details.fact.menu': 'Carta',
+1
View File
@@ -97,6 +97,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': 'Nejdřív vyprázdni pole popisu',
'places.details.facts': 'Dobré vědět',
'places.details.nothing': 'Pro toto místo se nic nenašlo.',
'places.details.fact.rating': 'Hodnocení',
'places.details.fact.cuisine': 'Kuchyně',
'places.details.fact.openingHours': 'Otevírací doba',
'places.details.fact.menu': 'Menu',
+1
View File
@@ -97,6 +97,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': 'Erst das Beschreibungsfeld leeren',
'places.details.facts': 'Gut zu wissen',
'places.details.nothing': 'Zu diesem Ort wurde nichts gefunden.',
'places.details.fact.rating': 'Bewertung',
'places.details.fact.cuisine': 'Küche',
'places.details.fact.openingHours': 'Öffnungszeiten',
'places.details.fact.menu': 'Speisekarte',
+1
View File
@@ -97,6 +97,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': 'Clear the description field first',
'places.details.facts': 'Good to know',
'places.details.nothing': 'Nothing found for this place.',
'places.details.fact.rating': 'Rating',
'places.details.fact.cuisine': 'Cuisine',
'places.details.fact.openingHours': 'Opening hours',
'places.details.fact.menu': 'Menu',
+1
View File
@@ -97,6 +97,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': 'Vacía primero el campo de descripción',
'places.details.facts': 'Bueno saber',
'places.details.nothing': 'No se encontró nada para este lugar.',
'places.details.fact.rating': 'Valoración',
'places.details.fact.cuisine': 'Cocina',
'places.details.fact.openingHours': 'Horario',
'places.details.fact.menu': 'Carta',
+1
View File
@@ -97,6 +97,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': "Videz d'abord le champ description",
'places.details.facts': 'Bon à savoir',
'places.details.nothing': 'Rien trouvé pour ce lieu.',
'places.details.fact.rating': 'Note',
'places.details.fact.cuisine': 'Cuisine',
'places.details.fact.openingHours': 'Horaires',
'places.details.fact.menu': 'Carte',
+1
View File
@@ -97,6 +97,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': 'Άδειασε πρώτα το πεδίο περιγραφής',
'places.details.facts': 'Καλό να ξέρεις',
'places.details.nothing': 'Δεν βρέθηκε τίποτα για αυτήν την τοποθεσία.',
'places.details.fact.rating': 'Βαθμολογία',
'places.details.fact.cuisine': 'Κουζίνα',
'places.details.fact.openingHours': 'Ωράριο',
'places.details.fact.menu': 'Μενού',
+1
View File
@@ -97,6 +97,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': 'Előbb ürítsd ki a leírás mezőt',
'places.details.facts': 'Jó tudni',
'places.details.nothing': 'Ehhez a helyhez nem található semmi.',
'places.details.fact.rating': 'Értékelés',
'places.details.fact.cuisine': 'Konyha',
'places.details.fact.openingHours': 'Nyitvatartás',
'places.details.fact.menu': 'Étlap',
+1
View File
@@ -96,6 +96,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': 'Kosongkan dulu kolom deskripsi',
'places.details.facts': 'Perlu diketahui',
'places.details.nothing': 'Tidak ada yang ditemukan untuk tempat ini.',
'places.details.fact.rating': 'Peringkat',
'places.details.fact.cuisine': 'Masakan',
'places.details.fact.openingHours': 'Jam buka',
'places.details.fact.menu': 'Menu',
+1
View File
@@ -97,6 +97,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': 'Svuota prima il campo descrizione',
'places.details.facts': 'Da sapere',
'places.details.nothing': 'Nessun risultato per questo luogo.',
'places.details.fact.rating': 'Valutazione',
'places.details.fact.cuisine': 'Cucina',
'places.details.fact.openingHours': 'Orari',
'places.details.fact.menu': 'Menu',
+1
View File
@@ -97,6 +97,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': '先に説明欄を空にしてください',
'places.details.facts': '知っておくと便利',
'places.details.nothing': 'この場所の情報は見つかりませんでした。',
'places.details.fact.rating': '評価',
'places.details.fact.cuisine': '料理',
'places.details.fact.openingHours': '営業時間',
'places.details.fact.menu': 'メニュー',
+1
View File
@@ -96,6 +96,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': '설명란을 먼저 비워 주세요',
'places.details.facts': '알아두면 좋아요',
'places.details.nothing': '이 장소에 대한 정보를 찾지 못했습니다.',
'places.details.fact.rating': '평점',
'places.details.fact.cuisine': '요리',
'places.details.fact.openingHours': '영업시간',
'places.details.fact.menu': '메뉴',
+1
View File
@@ -97,6 +97,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': 'Maak eerst het beschrijvingsveld leeg',
'places.details.facts': 'Goed om te weten',
'places.details.nothing': 'Niets gevonden voor deze plaats.',
'places.details.fact.rating': 'Beoordeling',
'places.details.fact.cuisine': 'Keuken',
'places.details.fact.openingHours': 'Openingstijden',
'places.details.fact.menu': 'Menukaart',
+1
View File
@@ -88,6 +88,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': 'Najpierw wyczyść pole opisu',
'places.details.facts': 'Warto wiedzieć',
'places.details.nothing': 'Nic nie znaleziono dla tego miejsca.',
'places.details.fact.rating': 'Ocena',
'places.details.fact.cuisine': 'Kuchnia',
'places.details.fact.openingHours': 'Godziny otwarcia',
'places.details.fact.menu': 'Menu',
+1
View File
@@ -97,6 +97,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': 'Сначала очистите поле описания',
'places.details.facts': 'Полезно знать',
'places.details.nothing': 'Для этого места ничего не найдено.',
'places.details.fact.rating': 'Рейтинг',
'places.details.fact.cuisine': 'Кухня',
'places.details.fact.openingHours': 'Часы работы',
'places.details.fact.menu': 'Меню',
+1
View File
@@ -97,6 +97,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': 'Töm beskrivningsfältet först',
'places.details.facts': 'Bra att veta',
'places.details.nothing': 'Inget hittades för den här platsen.',
'places.details.fact.rating': 'Betyg',
'places.details.fact.cuisine': 'Kök',
'places.details.fact.openingHours': 'Öppettider',
'places.details.fact.menu': 'Meny',
+1
View File
@@ -99,6 +99,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': 'Önce açıklama alanını boşalt',
'places.details.facts': 'Bilmekte fayda var',
'places.details.nothing': 'Bu yer için bir şey bulunamadı.',
'places.details.fact.rating': 'Puan',
'places.details.fact.cuisine': 'Mutfak',
'places.details.fact.openingHours': 'Çalışma saatleri',
'places.details.fact.menu': 'Menü',
+1
View File
@@ -97,6 +97,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': 'Спершу очистіть поле опису',
'places.details.facts': 'Корисно знати',
'places.details.nothing': 'Для цього місця нічого не знайдено.',
'places.details.fact.rating': 'Рейтинг',
'places.details.fact.cuisine': 'Кухня',
'places.details.fact.openingHours': 'Години роботи',
'places.details.fact.menu': 'Меню',
+1
View File
@@ -97,6 +97,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': 'Hãy xóa trống ô mô tả trước',
'places.details.facts': 'Thông tin hữu ích',
'places.details.nothing': 'Không tìm thấy gì cho địa điểm này.',
'places.details.fact.rating': 'Đánh giá',
'places.details.fact.cuisine': 'Ẩm thực',
'places.details.fact.openingHours': 'Giờ mở cửa',
'places.details.fact.menu': 'Thực đơn',
+1
View File
@@ -95,6 +95,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': '請先清空描述欄位',
'places.details.facts': '值得一看',
'places.details.nothing': '找不到此地點的資訊。',
'places.details.fact.rating': '評分',
'places.details.fact.cuisine': '菜系',
'places.details.fact.openingHours': '營業時間',
'places.details.fact.menu': '菜單',
+1
View File
@@ -95,6 +95,7 @@ const places: TranslationStrings = {
'places.details.adoptBlocked': '请先清空描述字段',
'places.details.facts': '值得一看',
'places.details.nothing': '未找到该地点的信息。',
'places.details.fact.rating': '评分',
'places.details.fact.cuisine': '菜系',
'places.details.fact.openingHours': '营业时间',
'places.details.fact.menu': '菜单',
+11
View File
@@ -147,6 +147,7 @@ export type PlaceDescription = z.infer<typeof placeDescriptionSchema>;
* `kind` is translated client-side; `value` is provider data and stays as-is.
*/
export const placeFactKindSchema = z.enum([
'rating',
'cuisine',
'openingHours',
'menu',
@@ -176,6 +177,16 @@ export const mapsPlaceEnrichmentRequestSchema = z.object({
/** Used to resolve a Wikipedia article when the place carries no wiki tag. */
name: z.string().min(1).max(300),
lang: z.string().max(35).optional(),
/**
* The place record the client already holds from picking the search result.
*
* Enrichment needs the same OSM tags that lookup returned, and fetching them
* again is not cheap: an Overpass lookup for a large relation was measured at
* 12.8 seconds. Passing them along turns a second slow round trip into none.
* Only the tags are read, and the wiki tag is re-validated before use, so a
* doctored payload can at worst mislead the user who sent it.
*/
details: z.record(z.string(), z.unknown()).optional(),
});
export type MapsPlaceEnrichmentRequest = z.infer<typeof mapsPlaceEnrichmentRequestSchema>;