Add more place types, refactor tag usage

This commit is contained in:
2026-02-23 17:53:46 +04:00
parent ecb3fe4b5a
commit 323aab8256
6 changed files with 113 additions and 42 deletions

View File

@@ -2,7 +2,7 @@ import Component from '@glimmer/component';
import { fn } from '@ember/helper'; import { fn } from '@ember/helper';
import { on } from '@ember/modifier'; import { on } from '@ember/modifier';
import { humanizeOsmTag } from '../utils/format-text'; import { humanizeOsmTag } from '../utils/format-text';
import { getLocalizedName } from '../utils/osm'; import { getLocalizedName, getPlaceType } from '../utils/osm';
import Icon from '../components/icon'; import Icon from '../components/icon';
import PlaceEditForm from './place-edit-form'; import PlaceEditForm from './place-edit-form';
@@ -47,38 +47,49 @@ export default class PlaceDetails extends Component {
} }
get type() { get type() {
const rawType = return getPlaceType(this.tags);
this.tags.amenity ||
this.tags.shop ||
this.tags.tourism ||
this.tags.leisure ||
this.tags.historic ||
'Point of Interest';
return humanizeOsmTag(rawType);
} }
get address() { get address() {
const t = this.tags; const t = this.tags;
const parts = []; const parts = [];
// Helper to get value from multiple keys
const get = (...keys) => {
for (const k of keys) {
if (t[k]) return t[k];
}
return null;
};
// Street + Number // Street + Number
if (t['addr:street']) { let street = get('addr:street', 'street');
let street = t['addr:street']; const number = get('addr:housenumber', 'housenumber');
if (t['addr:housenumber']) {
street += ` ${t['addr:housenumber']}`; if (street) {
if (number) {
street = `${street} ${number}`;
} }
parts.push(street); parts.push(street);
} }
// Postcode + City // Postcode + City
if (t['addr:city']) { let city = get('addr:city', 'city');
let city = t['addr:city']; const postcode = get('addr:postcode', 'postcode');
if (t['addr:postcode']) {
city = `${t['addr:postcode']} ${city}`; if (city) {
if (postcode) {
city = `${postcode} ${city}`;
} }
parts.push(city); parts.push(city);
} }
// State + Country (if not already covered)
const state = get('addr:state', 'state');
const country = get('addr:country', 'country');
if (state && state !== city) parts.push(state);
if (country) parts.push(country);
if (parts.length === 0) return null; if (parts.length === 0) return null;
return parts.join(', '); return parts.join(', ');
@@ -140,6 +151,16 @@ export default class PlaceDetails extends Component {
if (!id) return null; if (!id) return null;
return `https://www.google.com/maps/search/?api=1&query=${this.name}&query=${this.place.lat},${this.place.lon}`; return `https://www.google.com/maps/search/?api=1&query=${this.name}&query=${this.place.lat},${this.place.lon}`;
} }
get showDescription() {
// If it's a Photon result, the description IS the address.
// Since we are showing the address in the meta section (bottom),
// we should hide the description to avoid duplication.
if (this.place.source === 'photon') return false;
// Otherwise (e.g. saved place with custom description), show it.
return !!this.place.description;
}
<template> <template>
<div class="place-details"> <div class="place-details">
@@ -154,7 +175,7 @@ export default class PlaceDetails extends Component {
<p class="place-type"> <p class="place-type">
{{this.type}} {{this.type}}
</p> </p>
{{#if this.place.description}} {{#if this.showDescription}}
<p class="place-description"> <p class="place-description">
{{this.place.description}} {{this.place.description}}
</p> </p>

View File

@@ -4,10 +4,11 @@ import { action } from '@ember/object';
import { on } from '@ember/modifier'; import { on } from '@ember/modifier';
import { fn } from '@ember/helper'; import { fn } from '@ember/helper';
import or from 'ember-truth-helpers/helpers/or'; import or from 'ember-truth-helpers/helpers/or';
import eq from 'ember-truth-helpers/helpers/eq';
import PlaceDetails from './place-details'; import PlaceDetails from './place-details';
import Icon from './icon'; import Icon from './icon';
import humanizeOsmTag from '../helpers/humanize-osm-tag'; import humanizeOsmTag from '../helpers/humanize-osm-tag';
import { getLocalizedName } from '../utils/osm'; import { getLocalizedName, getPlaceType } from '../utils/osm';
export default class PlacesSidebar extends Component { export default class PlacesSidebar extends Component {
@service storage; @service storage;
@@ -186,16 +187,17 @@ export default class PlacesSidebar extends Component {
place.osmTags.name:en place.osmTags.name:en
"Unnamed Place" "Unnamed Place"
}}</div> }}</div>
<div class="place-type">{{humanizeOsmTag <div class="place-type">
(or {{#if (eq place.source "osm")}}
place.osmTags.amenity {{humanizeOsmTag place.type}}
place.osmTags.shop {{else}}
place.osmTags.tourism {{#if place.osmTags}}
place.osmTags.leisure {{humanizeOsmTag (getPlaceType place.osmTags)}}
place.osmTags.historic {{else if place.description}}
"Point of Interest" {{place.description}}
) {{/if}}
}}</div> {{/if}}
</div>
</button> </button>
</li> </li>
{{/each}} {{/each}}

View File

@@ -6,6 +6,8 @@ import { on } from '@ember/modifier';
import { fn } from '@ember/helper'; import { fn } from '@ember/helper';
import { task, timeout } from 'ember-concurrency'; import { task, timeout } from 'ember-concurrency';
import Icon from '#components/icon'; import Icon from '#components/icon';
import humanizeOsmTag from '../helpers/humanize-osm-tag';
import eq from 'ember-truth-helpers/helpers/eq';
export default class SearchBoxComponent extends Component { export default class SearchBoxComponent extends Component {
@service photon; @service photon;
@@ -178,8 +180,12 @@ export default class SearchBoxComponent extends Component {
</div> </div>
<div class="result-info"> <div class="result-info">
<span class="result-title">{{result.title}}</span> <span class="result-title">{{result.title}}</span>
{{#if result.description}} {{#if (eq result.source "osm")}}
<span class="result-desc">{{result.description}}</span> <span class="result-desc">{{humanizeOsmTag result.type}}</span>
{{else}}
{{#if result.description}}
<span class="result-desc">{{result.description}}</span>
{{/if}}
{{/if}} {{/if}}
</div> </div>
</button> </button>

View File

@@ -1,5 +1,5 @@
import Service, { service } from '@ember/service'; import Service, { service } from '@ember/service';
import { getLocalizedName } from '../utils/osm'; import { getLocalizedName, getPlaceType } from '../utils/osm';
export default class OsmService extends Service { export default class OsmService extends Service {
@service settings; @service settings;
@@ -61,15 +61,20 @@ out center;
} }
normalizePoi(poi) { normalizePoi(poi) {
const tags = poi.tags || {};
const type = getPlaceType(tags) || 'Point of Interest';
return { return {
title: getLocalizedName(poi.tags), title: getLocalizedName(tags),
lat: poi.lat || poi.center?.lat, lat: poi.lat || poi.center?.lat,
lon: poi.lon || poi.center?.lon, lon: poi.lon || poi.center?.lon,
url: poi.tags?.website, url: tags.website,
osmId: String(poi.id), osmId: String(poi.id),
osmType: poi.type, osmType: poi.type,
osmTags: poi.tags || {}, osmTags: tags,
description: poi.tags?.description, description: tags.description,
source: 'osm',
type: type,
}; };
} }
@@ -224,15 +229,20 @@ out center;
} }
} }
const tags = mainElement.tags || {};
const type = getPlaceType(tags) || 'Point of Interest';
return { return {
title: getLocalizedName(mainElement.tags), title: getLocalizedName(tags),
lat, lat,
lon, lon,
url: mainElement.tags?.website, url: tags.website,
osmId: String(mainElement.id), osmId: String(mainElement.id),
osmType: mainElement.type, osmType: mainElement.type,
osmTags: mainElement.tags || {}, osmTags: tags,
description: mainElement.tags?.description, description: tags.description,
source: 'osm',
type: type,
}; };
} }
} }

View File

@@ -1,4 +1,6 @@
import Service from '@ember/service'; import Service from '@ember/service';
import { getPlaceType } from '../utils/osm';
import { humanizeOsmTag } from '../utils/format-text';
export default class PhotonService extends Service { export default class PhotonService extends Service {
baseUrl = 'https://photon.komoot.io/api/'; baseUrl = 'https://photon.komoot.io/api/';
@@ -67,15 +69,24 @@ export default class PhotonService extends Service {
R: 'relation', R: 'relation',
}; };
const osmTags = { ...props };
// Photon often returns osm_key and osm_value for the main tag
if (props.osm_key && props.osm_value) {
osmTags[props.osm_key] = props.osm_value;
}
const type = getPlaceType(osmTags) || humanizeOsmTag(props.osm_value);
return { return {
title, title,
lat, lat,
lon, lon,
osmId: props.osm_id, osmId: props.osm_id,
osmType: osmTypeMap[props.osm_type] || props.osm_type, // 'node', 'way', 'relation' osmType: osmTypeMap[props.osm_type] || props.osm_type, // 'node', 'way', 'relation'
osmTags: props, // Keep all properties as tags for now osmTags,
description: props.name ? description : addressParts.slice(1).join(', '), description: props.name ? description : addressParts.slice(1).join(', '),
source: 'photon', source: 'photon',
type: type,
}; };
} }

View File

@@ -1,3 +1,5 @@
import { humanizeOsmTag } from './format-text';
export function getLocalizedName(tags, defaultName = 'Untitled Place') { export function getLocalizedName(tags, defaultName = 'Untitled Place') {
if (!tags) return defaultName; if (!tags) return defaultName;
@@ -30,3 +32,22 @@ export function getLocalizedName(tags, defaultName = 'Untitled Place') {
// 5. Final fallback // 5. Final fallback
return defaultName; return defaultName;
} }
export function getPlaceType(tags) {
if (!tags) return null;
const rawType =
tags.amenity ||
tags.shop ||
tags.tourism ||
tags.leisure ||
tags.office ||
tags.craft ||
tags.historic ||
tags.place ||
tags.building ||
tags.landuse ||
tags.natural;
return humanizeOsmTag(rawType);
}