Skip to content

enrichers: street-level imagery, since listing photos are not free - #145

Merged
ralyodio merged 1 commit into
mainfrom
worktree-nichedb-mapillary
Sep 25, 2026
Merged

ralyodio merged 1 commit into
mainfrom
worktree-nichedb-mapillary

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

You asked whether rental property images can come from anywhere. No for the interior, yes for the outside — this is the yes.

Why there's no free interior photo

Listing photography is locked up harder than listing data, for a different reason: a photograph carries its own copyright, owned by whoever took it, quite separately from any licence on the listing. There's no free source and there isn't going to be one.

Mapillary is the way round it for the street: crowdsourced imagery in 190+ countries, CC BY-SA 4.0, free with a token anybody can get. It won't show the kitchen. It shows the street, the block and what's opposite — which for somewhere a person is deciding whether to live is often the more useful picture, and is the part a listing's own photographs are least likely to show honestly.

Attribution isn't optional

CC BY-SA is share-alike, so every result stores the photographer's username, the image id, the licence and its URL — and the block says in as many words that the credit must be shown with the picture. Storing the URL without the credit would make this the same problem it exists to avoid.

It works for anything with a point

Coordinates are read in exactly the order the database's own ndb_geo_shape() uses — data.location, place, position, geometry, then the bare object. So an item this can place is precisely an item the geographic queries can already find, and it applies to crime reports and building permits as readily as to property.

  • (0, 0) is refused — it's in the Atlantic and means a missing coordinate
  • GeoJSON is handled as [lon, lat], the order people get wrong
  • bbox is 0.0012° either side (~130 m). Mapillary formalised a 0.01° ceiling in January 2026; this stays well inside it, and an image 400 m away isn't a picture of this address anyway

Probing the live endpoint caught a real bug before it shipped

Mapillary answers an invalid token with HTTP 500, not 401:

500 {"error":{"message":"Invalid OAuth 2.0 Access Token","code":190}}

My first version checked the status and would have reported "mapillary answered 500" — sending someone to Mapillary's status page instead of to their token. The body is now read first and code 190 is named as what it is, with a test for each of three cases: bad token as 500, bad token as 401, and a genuine outage that must not be called a bad token.

Also

openlisting now carries lat/lon onto data.location under the key names ndb_geo_shape reads, so a seller publishing coordinates gets street imagery with no further plumbing.

MAPILLARY_TOKEN is unset, so the enricher is simply off until one exists. Default-on for listings and housing.

Checks

32 new tests, 2,133 across the suite, lint clean. The usual 5 Redis/Postgres integration failures this dev box can't run.

🤖 Generated with Claude Code

Anthony asked whether rental property images can come from anywhere.
The answer is no for the interior and yes for the outside, and this is
the yes.

Listing photography is locked up harder than listing data, for a
different reason: a photograph carries its own copyright owned by
whoever took it, quite separately from any licence on the listing. So
there is no free source of interior photos and there is not going to be
one. Mapillary is the way round it for the street: crowdsourced imagery
in 190-odd countries under CC BY-SA 4.0, free with a token anybody can
get. It will not show the kitchen. It will show the street, the block
and what is opposite, which for somewhere a person is deciding whether
to live is often the more useful picture, and is the part a listing's
own photographs are least likely to show honestly.

CC BY-SA is share-alike, so attribution is not optional: the
photographer's username, the image id, the licence and its URL are
stored on every result, and the stored block says in as many words that
the credit must be shown with the picture. Storing the URL without the
credit would make this the same problem it exists to avoid.

Coordinates are read in exactly the order the database's own
ndb_geo_shape() uses -- data.location, place, position, geometry, then
the bare object -- so an item this can place is precisely an item the
geographic queries can already find, and it works for anything with a
point, not just property. (0, 0) is refused: it is in the Atlantic and
means a missing coordinate. GeoJSON is handled as [lon, lat], which is
the order people get wrong.

The bbox is 0.0012 degrees either side, about 130 m. Mapillary
formalised a 0.01-degree ceiling in January 2026; this stays well
inside it, and more to the point an image four hundred metres away is
not a picture of this address.

**Probing the live endpoint caught a real bug before it shipped.**
Mapillary answers an invalid token with HTTP **500**, not 401:

  500 {"error":{"message":"Invalid OAuth 2.0 Access Token","code":190}}

The first version checked the status and would have reported "mapillary
answered 500", sending somebody to Mapillary's status page instead of
to their token. The body is now read first and code 190 is named as
what it is, with a test for each of the three cases: bad token as 500,
bad token as 401, and a genuine outage that must NOT be called a bad
token.

openlisting now carries lat/lon through onto data.location, under the
key names ndb_geo_shape reads, so a seller publishing coordinates gets
street imagery with no further plumbing.

MAPILLARY_TOKEN is unset here, so the enricher is simply off until one
exists. Default-on for listings and housing.

32 new tests, 2133 across the suite, lint clean. The usual 5
Redis/Postgres integration failures this box cannot run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ralyodio
ralyodio marked this pull request as ready for review September 25, 2026 10:49
@ralyodio
ralyodio merged commit a8332de into main Sep 25, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant