From f2c2eb1fdcc28750c7d318371dacffea4f1249b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 29 Jun 2026 15:54:37 +0200 Subject: [PATCH 1/8] Add icon for amenity=townhall --- app/utils/icons.js | 2 ++ app/utils/osm-icons.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/utils/icons.js b/app/utils/icons.js index f92ecf3..b6cc916 100644 --- a/app/utils/icons.js +++ b/app/utils/icons.js @@ -61,6 +61,7 @@ import boxingGloveUp from '@waysidemapping/pinhead/dist/icons/boxing_glove_up.sv import car from '@waysidemapping/pinhead/dist/icons/car.svg?raw'; import cigaretteWithSmokeCurl from '@waysidemapping/pinhead/dist/icons/cigarette_with_smoke_curl.svg?raw'; import classicalBuilding from '@waysidemapping/pinhead/dist/icons/classical_building.svg?raw'; +import classicalBuildingWithClock from '@waysidemapping/pinhead/dist/icons/classical_building_with_clock.svg?raw'; import classicalBuildingWithDomeAndFlag from '@waysidemapping/pinhead/dist/icons/classical_building_with_dome_and_flag.svg?raw'; import classicalBuildingWithFlag from '@waysidemapping/pinhead/dist/icons/classical_building_with_flag.svg?raw'; import commercialBuilding from '@waysidemapping/pinhead/dist/icons/commercial_building.svg?raw'; @@ -162,6 +163,7 @@ const ICONS = { 'alert-circle': alertCircle, 'alert-triangle': alertTriangle, 'classical-building': classicalBuilding, + 'classical-building-with-clock': classicalBuildingWithClock, 'classical-building-with-dome-and-flag': classicalBuildingWithDomeAndFlag, 'classical-building-with-flag': classicalBuildingWithFlag, 'commercial-building': commercialBuilding, diff --git a/app/utils/osm-icons.js b/app/utils/osm-icons.js index f4be54e..b55d471 100644 --- a/app/utils/osm-icons.js +++ b/app/utils/osm-icons.js @@ -27,6 +27,8 @@ export const POI_ICON_RULES = [ { tags: { amenity: 'bank' }, icon: 'banknote' }, { tags: { amenity: 'place_of_worship' }, icon: 'place-of-worship-building' }, + { tags: { amenity: 'townhall' }, icon: 'classical-building-with-clock' }, + { tags: { building: 'townhall' }, icon: 'classical-building-with-clock' }, { tags: { amenity: 'fire_station' }, icon: 'badge-shield-with-fire' }, { tags: { amenity: 'police' }, icon: 'police-officer-with-stop-arm' }, { tags: { amenity: 'toilets' }, icon: 'womens-and-mens-restroom-symbol' }, -- 2.50.1 From 5b8bec6a000957bf424223708638a08d7486ef4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 29 Jun 2026 16:06:55 +0200 Subject: [PATCH 2/8] Cut off overlong sidebar link texts with ellipses --- app/styles/app.css | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/styles/app.css b/app/styles/app.css index 1aafd6b..14a1b97 100644 --- a/app/styles/app.css +++ b/app/styles/app.css @@ -1266,6 +1266,20 @@ span.icon { gap: 0.5rem; } +.content-with-icon > span:not(.icon) { + min-width: 0; + flex: 1; +} + +.content-with-icon > span:not(.icon) a { + display: inline-block; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: bottom; +} + .content-with-icon .icon { margin-top: 0.15rem; } -- 2.50.1 From 86d25dc6ba23bf3c2007cc415a40ce6de07965bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 29 Jun 2026 16:28:55 +0200 Subject: [PATCH 3/8] Add OSM links for custom saved places Link to the OSM search route, so we get a pin when opening OSM from Marco --- app/components/place-details.gjs | 21 ++++++-- .../components/place-details-test.gjs | 50 +++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/app/components/place-details.gjs b/app/components/place-details.gjs index 0c6bb2f..a118445 100644 --- a/app/components/place-details.gjs +++ b/app/components/place-details.gjs @@ -23,6 +23,7 @@ export default class PlaceDetails extends Component { @service storage; @service nostrAuth; @service nostrData; + @service mapUi; @tracked isEditing = false; @tracked showLists = false; @tracked isPhotoUploadActive = false; @@ -345,9 +346,21 @@ export default class PlaceDetails extends Component { get osmUrl() { const id = this.place.osmId; - if (!id) return null; - const type = this.place.osmType || 'node'; - return `https://www.openstreetmap.org/${type}/${id}`; + if (id) { + const type = this.place.osmType || 'node'; + return `https://www.openstreetmap.org/${type}/${id}`; + } + + const lat = this.place.lat; + const lon = this.place.lon; + if (!lat || !lon) return null; + + const viewLat = this.mapUi.currentCenter?.lat ?? lat; + const viewLon = this.mapUi.currentCenter?.lon ?? lon; + const zoom = this.mapUi.currentZoom ?? 17; + const roundedZoom = Math.round(zoom); + + return `https://www.openstreetmap.org/search?lat=${lat}&lon=${lon}&zoom=${roundedZoom}#map=${roundedZoom}/${Number(viewLat).toFixed(5)}/${Number(viewLon).toFixed(5)}`; } get gmapsUrl() { @@ -591,7 +604,7 @@ export default class PlaceDetails extends Component { - {{#if this.osmUrl}} + {{#if this.place.osmId}}

diff --git a/tests/integration/components/place-details-test.gjs b/tests/integration/components/place-details-test.gjs index 6969046..6a70d2b 100644 --- a/tests/integration/components/place-details-test.gjs +++ b/tests/integration/components/place-details-test.gjs @@ -334,4 +334,54 @@ module('Integration | Component | place-details', function (hooks) { assert.dom(links[0]).hasText('+44 987 654 321'); assert.dom(links[1]).hasText('+1 234-567 8900'); }); + + test('it renders correct OpenStreetMap link for an OSM place', async function (assert) { + const place = { + title: 'OSM Place', + osmId: '12345', + osmType: 'node', + lat: 52.520008, + lon: 13.404954, + }; + + await render(); + + const osmLink = this.element.querySelector( + '.meta-info a[href^="https://www.openstreetmap.org/node/12345"]' + ); + assert.ok(osmLink, 'OpenStreetMap link is rendered'); + assert.strictEqual( + osmLink.getAttribute('href'), + 'https://www.openstreetmap.org/node/12345' + ); + + assert.dom('button.btn-link').hasText('Add a photo'); + }); + + test('it renders correct search-based OpenStreetMap link for a custom saved place', async function (assert) { + class MockMapUi extends Service { + currentCenter = { lat: 52.5, lon: 13.4 }; + currentZoom = 15.6; + } + this.owner.register('service:map-ui', MockMapUi); + + const place = { + title: 'Custom Place', + lat: 52.520008, + lon: 13.404954, + }; + + await render(); + + const osmLink = this.element.querySelector( + '.meta-info a[href^="https://www.openstreetmap.org/search"]' + ); + assert.ok(osmLink, 'OpenStreetMap search link is rendered'); + assert.strictEqual( + osmLink.getAttribute('href'), + 'https://www.openstreetmap.org/search?lat=52.520008&lon=13.404954&zoom=16#map=16/52.50000/13.40000' + ); + + assert.dom('button.btn-link').doesNotExist(); + }); }); -- 2.50.1 From 18bda60310c201b2a9da62fa8f91e1c7814096c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 29 Jun 2026 17:02:43 +0200 Subject: [PATCH 4/8] Add guest houses to hotel quick search --- app/utils/poi-categories.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/utils/poi-categories.js b/app/utils/poi-categories.js index 2eeb91c..ba8f739 100644 --- a/app/utils/poi-categories.js +++ b/app/utils/poi-categories.js @@ -55,7 +55,7 @@ export const POI_CATEGORIES = [ id: 'accommodation', label: 'Hotels', icon: 'person-sleeping-in-bed', - filter: ['["tourism"~"^(hotel|hostel|motel|chalet)$"]'], + filter: ['["tourism"~"^(hotel|hostel|motel|chalet|guest_house)$"]'], types: ['node', 'way', 'relation'], }, ]; -- 2.50.1 From c33fe3b2682eecc565e6cf63902a8a327b4db8b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 29 Jun 2026 17:08:55 +0200 Subject: [PATCH 5/8] Add icon for car repair shops --- app/utils/icons.js | 2 ++ app/utils/osm-icons.js | 1 + 2 files changed, 3 insertions(+) diff --git a/app/utils/icons.js b/app/utils/icons.js index b6cc916..5a71d2c 100644 --- a/app/utils/icons.js +++ b/app/utils/icons.js @@ -59,6 +59,7 @@ import bus from '@waysidemapping/pinhead/dist/icons/bus.svg?raw'; import camera from '@waysidemapping/pinhead/dist/icons/camera.svg?raw'; import boxingGloveUp from '@waysidemapping/pinhead/dist/icons/boxing_glove_up.svg?raw'; import car from '@waysidemapping/pinhead/dist/icons/car.svg?raw'; +import carAndWrench from '@waysidemapping/pinhead/dist/icons/car_and_wrench.svg?raw'; import cigaretteWithSmokeCurl from '@waysidemapping/pinhead/dist/icons/cigarette_with_smoke_curl.svg?raw'; import classicalBuilding from '@waysidemapping/pinhead/dist/icons/classical_building.svg?raw'; import classicalBuildingWithClock from '@waysidemapping/pinhead/dist/icons/classical_building_with_clock.svg?raw'; @@ -264,6 +265,7 @@ const ICONS = { winding_way_wide: windingWayWide, parking_p: parkingP, car, + 'car-and-wrench': carAndWrench, x, zap, 'loading-ring': loadingRing, diff --git a/app/utils/osm-icons.js b/app/utils/osm-icons.js index b55d471..976acf4 100644 --- a/app/utils/osm-icons.js +++ b/app/utils/osm-icons.js @@ -74,6 +74,7 @@ export const POI_ICON_RULES = [ tags: { shop: 'beauty' }, icon: 'fancy-mirror-with-reflection-and-stars', }, + { tags: { shop: 'car_repair' }, icon: 'car-and-wrench' }, { tags: { craft: 'tailor' }, icon: 'needle-and-spool-of-thread' }, { tags: { office: 'estate_agent' }, icon: 'village-buildings' }, { tags: { office: true }, icon: 'commercial-building' }, -- 2.50.1 From 0bcbae374b346c98acc9c1b96ea117673f20fb62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 29 Jun 2026 17:28:00 +0200 Subject: [PATCH 6/8] Use "yes" tag values only as fallbacks if there isn't a more specific OSM key --- app/utils/osm.js | 11 ++++++++++- tests/unit/utils/osm-test.js | 6 ++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/utils/osm.js b/app/utils/osm.js index b16295c..e0a16b4 100644 --- a/app/utils/osm.js +++ b/app/utils/osm.js @@ -56,15 +56,24 @@ const PLACE_TYPE_KEYS = [ export function getPlaceType(tags) { if (!tags) return null; + let fallbackKey = null; + for (const key of PLACE_TYPE_KEYS) { const value = tags[key]; if (value) { if (value === 'yes') { - return humanizeOsmTag(key); + if (!fallbackKey) { + fallbackKey = key; + } + continue; } return humanizeOsmTag(value); } } + if (fallbackKey) { + return humanizeOsmTag(fallbackKey); + } + return null; } diff --git a/tests/unit/utils/osm-test.js b/tests/unit/utils/osm-test.js index 939937e..97acf83 100644 --- a/tests/unit/utils/osm-test.js +++ b/tests/unit/utils/osm-test.js @@ -89,6 +89,12 @@ module('Unit | Utility | osm', function (hooks) { assert.strictEqual(result, 'Building'); }); + test('getPlaceType ignores generic "yes" values if a more specific tag is present', function (assert) { + const tags = { historic: 'yes', building: 'tower' }; + const result = getPlaceType(tags); + assert.strictEqual(result, 'Tower'); + }); + test('getPlaceType prioritizes order (amenity > shop > building)', function (assert) { // If something is both a shop and a building, it should be a shop const tags = { building: 'yes', shop: 'supermarket' }; -- 2.50.1 From 448c51bab6304bea5fa9221cdcc3426c76669293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 29 Jun 2026 17:28:57 +0200 Subject: [PATCH 7/8] Add icons for city gates and historic towers --- app/utils/icons.js | 4 ++++ app/utils/osm-icons.js | 2 ++ 2 files changed, 6 insertions(+) diff --git a/app/utils/icons.js b/app/utils/icons.js index 5a71d2c..88e3c66 100644 --- a/app/utils/icons.js +++ b/app/utils/icons.js @@ -60,7 +60,9 @@ import camera from '@waysidemapping/pinhead/dist/icons/camera.svg?raw'; import boxingGloveUp from '@waysidemapping/pinhead/dist/icons/boxing_glove_up.svg?raw'; import car from '@waysidemapping/pinhead/dist/icons/car.svg?raw'; import carAndWrench from '@waysidemapping/pinhead/dist/icons/car_and_wrench.svg?raw'; +import castleKeep from '@waysidemapping/pinhead/dist/icons/castle_keep.svg?raw'; import cigaretteWithSmokeCurl from '@waysidemapping/pinhead/dist/icons/cigarette_with_smoke_curl.svg?raw'; +import cityGate from '@waysidemapping/pinhead/dist/icons/city_gate.svg?raw'; import classicalBuilding from '@waysidemapping/pinhead/dist/icons/classical_building.svg?raw'; import classicalBuildingWithClock from '@waysidemapping/pinhead/dist/icons/classical_building_with_clock.svg?raw'; import classicalBuildingWithDomeAndFlag from '@waysidemapping/pinhead/dist/icons/classical_building_with_dome_and_flag.svg?raw'; @@ -159,6 +161,7 @@ const ICONS = { 'chevron-left': chevronLeft, 'chevron-right': chevronRight, 'cigarette-with-smoke-curl': cigaretteWithSmokeCurl, + 'city-gate': cityGate, climbing_wall: climbingWall, check, 'alert-circle': alertCircle, @@ -266,6 +269,7 @@ const ICONS = { parking_p: parkingP, car, 'car-and-wrench': carAndWrench, + 'castle-keep': castleKeep, x, zap, 'loading-ring': loadingRing, diff --git a/app/utils/osm-icons.js b/app/utils/osm-icons.js index 976acf4..6df8f10 100644 --- a/app/utils/osm-icons.js +++ b/app/utils/osm-icons.js @@ -116,7 +116,9 @@ export const POI_ICON_RULES = [ { tags: { historic: 'bridge' }, icon: 'bridge' }, { tags: { historic: 'bridge_site' }, icon: 'bridge' }, { tags: { historic: 'fort' }, icon: 'fort' }, + { tags: { historic: 'city_gate' }, icon: 'city-gate' }, { tags: { historic: 'castle' }, icon: 'palace' }, + { tags: { building: 'tower', historic: 'yes' }, icon: 'castle-keep' }, { tags: { historic: 'building' }, icon: 'classical-building-with-flag' }, { tags: { historic: 'archaeological_site' }, icon: 'grecian-vase' }, { tags: { historic: 'memorial' }, icon: 'memorial-stone-with-inscription' }, -- 2.50.1 From f01730fef594f7bda2f003d992094dbb9b155c76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 29 Jun 2026 17:32:26 +0200 Subject: [PATCH 8/8] Add icon and quick search results for tourist information --- app/utils/icons.js | 2 ++ app/utils/osm-icons.js | 1 + app/utils/poi-categories.js | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/utils/icons.js b/app/utils/icons.js index 88e3c66..5744072 100644 --- a/app/utils/icons.js +++ b/app/utils/icons.js @@ -92,6 +92,7 @@ import grecianVase from '@waysidemapping/pinhead/dist/icons/grecian_vase.svg?raw import greekCross from '@waysidemapping/pinhead/dist/icons/greek_cross.svg?raw'; import iceCreamOnCone from '@waysidemapping/pinhead/dist/icons/ice_cream_on_cone.svg?raw'; import industrialBuilding from '@waysidemapping/pinhead/dist/icons/industrial_building.svg?raw'; +import infoI from '@waysidemapping/pinhead/dist/icons/info_i.svg?raw'; import jewel from '@waysidemapping/pinhead/dist/icons/jewel.svg?raw'; import lowriseBuilding from '@waysidemapping/pinhead/dist/icons/lowrise_building.svg?raw'; import marketStall from '@waysidemapping/pinhead/dist/icons/market_stall.svg?raw'; @@ -204,6 +205,7 @@ const ICONS = { 'ice-cream-on-cone': iceCreamOnCone, 'industrial-building': industrialBuilding, info, + 'info-i': infoI, instagram, jewel, 'log-in': logIn, diff --git a/app/utils/osm-icons.js b/app/utils/osm-icons.js index 6df8f10..4b4741e 100644 --- a/app/utils/osm-icons.js +++ b/app/utils/osm-icons.js @@ -106,6 +106,7 @@ export const POI_ICON_RULES = [ { tags: { tourism: 'viewpoint' }, icon: 'camera' }, { tags: { tourism: 'zoo' }, icon: 'camera' }, { tags: { tourism: 'artwork' }, icon: 'camera' }, + { tags: { tourism: 'information' }, icon: 'info-i' }, { tags: { amenity: 'cinema' }, icon: 'film' }, { tags: { amenity: 'theatre' }, icon: 'camera' }, { tags: { amenity: 'arts_centre' }, icon: 'comedy-mask-and-tragedy-mask' }, diff --git a/app/utils/poi-categories.js b/app/utils/poi-categories.js index ba8f739..fe1cf92 100644 --- a/app/utils/poi-categories.js +++ b/app/utils/poi-categories.js @@ -43,7 +43,7 @@ export const POI_CATEGORIES = [ label: 'Things to do', icon: 'feather-camera', filter: [ - '["tourism"~"^(museum|gallery|attraction|viewpoint|zoo|theme_park|aquarium|artwork)$"]', + '["tourism"~"^(museum|gallery|attraction|viewpoint|zoo|theme_park|aquarium|artwork|information)$"]', '["amenity"~"^(cinema|theatre|arts_centre|planetarium)$"]', '["leisure"~"^(sports_centre|stadium|water_park)$"]', '["historic"]', -- 2.50.1