SSMTour — an OSM-based travel guide and deterministic itinerary planner (106 cities)

I have been building SSMTour, a travel guide and self-guided itinerary planner that runs entirely on open data. It now covers 106 cities in 54 countries, in 11 languages.

What comes from OSM: the places themselves, their categories, opening_hours, the wheelchair/toilet/changing-table tags, and district boundaries used to place each POI in the right district. Descriptive text and images come from Wikidata and Wikimedia Commons, credited per photograph. Attribution and the ODbL notice are in the footer of every page.

The part that may interest this forum is the planner. It is deterministic — no language model runs at request time. It reads opening_hours, a per-category default visit duration, and a travel-time matrix, then fills each day under two hard invariants: never schedule a stop while its POI is closed, and never exceed the day’s time budget once visit time, travel time and breaks are counted.

The hard case turned out to be sparsely mapped cities rather than dense ones. Manisa is deliberately in the set for that reason: with a thin POI pool and a lot of missing opening_hours, every assumption a dense city hides becomes visible — category-default durations especially.

Two things I would genuinely like feedback on:

  • opening_hours parsing. Real values are messier than the spec, and a day with a midday break is the case I have got wrong most often.
  • Whether the category mapping from OSM tags reads sensibly to people who actually map these places. I already found one where leisure=park on a tomb outranked its religious tags.
1 Like

quite interesting, it was not too bad what it suggested for 3 days in Rome. Maybe a bit optimistic as some sites will have queues and it seems this is not contemplated yet.

What is probably too short is 1.5hrs for the Roman Forum, especially if you would then stay for 2 on Piazza del Popolo:

how do you choose the restaurants? I believe there should be some options (e.g. cuisine, price, are you willing to spend double because the restaurant is at a famous tourist location. I didn’t know Babington’s Tea Room, but it’s apparently there since 1893, had a look on their menu, sandwiches and burgers at about 25-30 eur, cream tea for two (with 2 scones, butter and marmalade) 52 euros, that’s pretty expensive in my view.

What doesn’t work is the initial map scale: it shows the day on a map (all in one city, walks), but the scale is southern europe.

1 Like

Could you be more specific about what you want feedback on? It doesn’t seem very efficient for people here to cross check random attractions against real opening hours. You mention midday breaks - in principle those should be handled, if there is some systematic issue you probably need to point us to examples.

The first restaurant suggested for my Dublin query is worse: the only lunch option is the set menu at 95 euros for 1 person! As far as I could see the site gave no hint that is it is suggesting one of the most expensive restaurants in the city. So I agree this aspect needs some work.

1 Like

also: who would stay 3 days in Rome and have lunch at a tea room? Wouldn’t you want to taste some local food?

1 Like

Thank you both — every one of these held up when I went and checked, and three are now fixed.

The map scale. You were right and the cause was not the scale logic. The map container is height: 100%, so at the moment the script initialises it the box can still be zero, and Leaflet fits the bounds against that: the centre was correct and the zoom was not. It now measures the container before fitting and refits once the container is actually laid out.

Visit durations. 1.5 hours for the Roman Forum was short, and reading the rest of the data showed the shape of the mistake: 268 of 289 editorially set “activity” durations were exactly 120 minutes, so every public square was a two-hour visit — Piazza Mattei, which is a fountain and a few façades, the same as Piazza Navona. Eleven squares are now 30 minutes and the Forum is 150. Thank you for that one; I would not have found it from a passing glance at Rome alone.

Restaurants. This is the one I am most grateful for, because the cause was mine and recent. Two days ago I tightened which places get a published page — an advertising review had flagged thin pages — and the rule keeps a place only if it has a Wikidata item, editorial standing or written text. The planner drew its meal candidates from that same published set. Among restaurants, “has a Wikidata item” means famous, so Dublin’s pool collapsed to six venues, all of them among the city’s most expensive. That is exactly why @alan_gr was offered a €95 set menu and why Babington’s turned up in Rome.

A restaurant can be a fair place to eat without deserving an indexed page of its own, so meal candidates no longer require that. Dublin goes from 6 venues to 58.

On price and cuisine specifically: OSM carries no price tag on any of the 1780 food POIs I hold, and only 254 carry cuisine, so I have nothing real to filter on. Rather than invent a price band, the meal stop now says plainly what it is — nearby, open at that hour, and not a recommendation, with no price or menu recorded. If there is a tagging practice for price that I have simply missed, I would like to know.

Rome is a separate problem I found while checking this: I hold 5 food POIs for Rome against 181 for London and 190 for Tokyo, so the ingest for that city is plainly incomplete and the planner was choosing from almost nothing. I am re-running it.

Queues. Not modelled, and you are right that it matters. I have not found a source for it in OSM or Wikidata, and a made-up average would quietly break the day’s time budget rather than admit the gap, so for now the plan simply does not know about queues. If anyone knows of a usable open source for typical waiting times, I would use it.

@alan_gr — fair point on my question, it was too vague to act on. What I actually want is narrower: I parse opening_hours myself rather than using an existing library, and the case I keep getting wrong is a day written as two intervals around a midday break, Tu-Su 09:00-13:00,15:00-19:00. I render that as one span with the break named separately rather than showing only the first interval, but I am not confident about the messier real-world variants — off, PH, seasonal ranges, and values where the separator or ordering is unusual. If it is useful I can post the specific strings my parser rejects, from real objects, rather than asking anyone to cross-check attractions by hand.

1 Like

If you want technical help on parsing opening hours I’d suggest posting specifically on that topic. There must be lots of applications and libraries that do this and it’s not clear to me if you really need to reinvent this from scratch. But I’m not a developer so can’t help in detail.

There is very little systematic tagging of restaurant prices in OSM other than implicitly via links to websites.

Similar to the point about queues, I wonder if time waiting for public transport is allowed for (on top of time travelling). For buses in Dublin you can’t always expect a bus to turn up immediately. Generally I have the impression that timings are very tight.

As a general point, your last post takes a long time to read and it’s not clear how much is relevant. I’d suggest focusing your questions more and avoiding sounding like Claude explaining in extreme detail how it got things wrong.

1 Like

Fair on all counts, and point taken on the length.

Waiting is allowed for, but crudely: a flat 10 minutes on a boarded leg and 15 on a road one, the same everywhere, with no line frequency behind it. That is optimistic for Dublin buses, which is probably what you were feeling. The leg now shows the wait separately rather than folding it into one number, so the assumption is at least visible.

Thanks for confirming the price situation — that settles it, and I will leave price alone rather than guess at it.

I will take opening_hours to its own topic with the actual strings.

A focused update on opening_hours: I stopped treating missing hours as implicit availability. In the latest data pass, three cases were actively misleading — a park with race-day shortened hours, a museum with seasonal hours, and a museum temporarily closed for renovation. The planner now uses explicit closed intervals or suppresses stale static rows, so a known-closed place cannot be scheduled.

For places where the operator publishes a monthly exception calendar (race days, barrier trials or track work) on top of recurring hours, do mappers commonly use opening_hours:conditional, or is that kind of short-lived calendar better left outside OSM? I do not want to copy transient dates into OSM if they do not belong there.

My 2 cents:

I don’t believe so. See TagInfo

That said, “conditional” opening hours can often be coded into the regular opening_hours tag

That depends. If incidental then probably not, but no hard no from me. If they happen on “fixed” days like every second Sunday of the month or something like that then yes, but that can be coded as said above into the opening_hours tag

In principle yes. But it can be difficult to get the right syntax. And again can probably go into the regular tag.

Again, that depends. On the duration in this case. short durations like a couple of days or weeks then no. But longer durations like several months then probably yes. But there are no real hard rules about how long the duration should be before tagging it. And this can also probably be coded in to the regular tag.


Example:

A tag like this opening_hours:conditional=closed @ (2026 Jan 01-2027 Jan 01)[1] could be re-coded to opening_hours=2026 Jan 01-2027 Jan 01: closed

Sample:
Mo-Fr 09:00-18:00; 2026 Jan 01-2027 Jan 01: closed is considered valid in the evaluation tool.

See around:

The date and time bar at the top of the evaluation tool can be used to “walk” trough different dates.

edit: Oops, wrong link for TagInfo.


  1. found in TagInfo ↩︎