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 { on } from '@ember/modifier';
import { humanizeOsmTag } from '../utils/format-text';
import { getLocalizedName } from '../utils/osm';
import { getLocalizedName, getPlaceType } from '../utils/osm';
import Icon from '../components/icon';
import PlaceEditForm from './place-edit-form';
@@ -47,38 +47,49 @@ export default class PlaceDetails extends Component {
}
get type() {
const rawType =
this.tags.amenity ||
this.tags.shop ||
this.tags.tourism ||
this.tags.leisure ||
this.tags.historic ||
'Point of Interest';
return humanizeOsmTag(rawType);
return getPlaceType(this.tags);
}
get address() {
const t = this.tags;
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
if (t['addr:street']) {
let street = t['addr:street'];
if (t['addr:housenumber']) {
street += ` ${t['addr:housenumber']}`;
let street = get('addr:street', 'street');
const number = get('addr:housenumber', 'housenumber');
if (street) {
if (number) {
street = `${street} ${number}`;
}
parts.push(street);
}
// Postcode + City
if (t['addr:city']) {
let city = t['addr:city'];
if (t['addr:postcode']) {
city = `${t['addr:postcode']} ${city}`;
let city = get('addr:city', 'city');
const postcode = get('addr:postcode', 'postcode');
if (city) {
if (postcode) {
city = `${postcode} ${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;
return parts.join(', ');
@@ -140,6 +151,16 @@ export default class PlaceDetails extends Component {
if (!id) return null;
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>
<div class="place-details">
@@ -154,7 +175,7 @@ export default class PlaceDetails extends Component {
<p class="place-type">
{{this.type}}
</p>
{{#if this.place.description}}
{{#if this.showDescription}}
<p class="place-description">
{{this.place.description}}
</p>

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,6 @@
import Service from '@ember/service';
import { getPlaceType } from '../utils/osm';
import { humanizeOsmTag } from '../utils/format-text';
export default class PhotonService extends Service {
baseUrl = 'https://photon.komoot.io/api/';
@@ -67,15 +69,24 @@ export default class PhotonService extends Service {
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 {
title,
lat,
lon,
osmId: props.osm_id,
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(', '),
source: 'photon',
type: type,
};
}

View File

@@ -1,3 +1,5 @@
import { humanizeOsmTag } from './format-text';
export function getLocalizedName(tags, defaultName = 'Untitled Place') {
if (!tags) return defaultName;
@@ -30,3 +32,22 @@ export function getLocalizedName(tags, defaultName = 'Untitled Place') {
// 5. Final fallback
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);
}