Skip to content

The Validation Gate Before Third-Party Photos Enter a Grid

Adityo Guni Waluyo

Name matching first, image checked after load, revert when in doubt: the validation gate that keeps third-party photos from poisoning a public grid.

TL;DR

Images were missing from a culture grid. Entity data was healthy, but image supply was the issue. A solution was found, but with limitations and a need for further verification.

The Grid That Wasn't I stared at the culture grid on KotaPortal, half-empty: cards were there, titles were there, but images were nowhere to be found. Entity data was healthy in the API, so the problem was purely image supply. The venue owners had photos on Google Maps. The quickest solution was to open their Maps page, grab the first hero image, download the 1200px version, save it locally, and fill in the image column. Easy peasy, I thought, this would be done in one afternoon.

Reality had other plans. Mid-process, I found cultural entities whose photos turned out to be a furniture store, a mall interior, and a photo from the wrong city. Their names were similar in search, but their pages were completely different. If these slipped through, readers would trust the grid and get wrong information without ever knowing.

Name First, Image Later The first gate wasn't about images but identity: the Maps page title had to match the entity name in the database before any image was accepted. This sounds strict, but mapping errors that slip through are far more costly than empty cards. Out of 12 entities processed, 7 passed and were saved, 5 were reverted to their image-less state because they couldn't be verified. One of the errors was recovered through a re-query; the rest weren't. And by the third round, Google started rate-limiting my requests. This limit wasn't a hindrance but a signal to stop guessing.

After passing the name gate, I did a technical image check using one signal: `naturalWidth === 0`. The `naturalWidth` property returns the intrinsic width of an image in pixels [4], and its value is zero when image data is unavailable, meaning the image is broken or empty. The condition was simple: the check had to be done after the load or error event was complete, not during loading, or slow images would be mislabeled as broken. I also gave the thumbnail grid `loading="lazy"`: non-critical resources were deferred until they were near the viewport [2], so 12 cards didn't mean 12 downloads in the first second.

Google URLs Are Pipes, Not Storage Another assumption that fell apart was that Google photo URLs are permanent. The official channel itself rejects this assumption: `photo_reference` cannot be cached and may expire [3]. Google photo URLs are display pipes, not storage.

The most honest part to admit is that saving local copies of Maps photos is akin to extracting content from their service. The terms of use are clear, "Customer will not export, extract, or otherwise scrape Google Maps Content for use outside the Services" [1]. Google even provides a visual signal about the photo's origin: names or attribution icons distinguish Google-taken photos from external contributor photos [5]. This means practically that backfilled photos like these are emergency bridges, not permanent assets. They should be gradually replaced with official photos from the venue owners, and it should be honestly noted in the log that their licensing is unclear.

Mass Changes Need Brakes Filling dozens of data rows in one session is a mass operation, and mass operations without brakes are just waiting to become horror stories. I installed three layers of brakes. First, an SQL dump before execution, a human-readable rollback path, not just a log. Then, a recount via API afterward: 7 entities brought images, the rest returned to their image-less state. Finally, visual verification per card, ensuring the on-screen name matched the photo content, one by one, not just checking if an image existed.

One small trap that almost slipped through was the API payload placing images in the `thumbnail` field, not the main image field. The next API consumer assuming otherwise would draw an empty grid, exactly the problem that started this job.

Third-party photos are cheap until one of them is wrong. The validation gate (name first, image checked after loading, return if in doubt, dump before mass execution) isn't additional bureaucracy. It's a cheap price for a trustworthy grid, far cheaper than a whole morning spent tracing incorrect photos from the start.

Sources

[1] Google Maps Platform Terms of Service · [2] MDN: Lazy loading · [3] Google Place Photos (Legacy) · [4] MDN: HTMLImageElement.naturalWidth · [5] Maps User Contributed Content Policy