More about the Icecream Map

As discussed previously my ice cream shop review blog has a map page based on MapLibre GL JS. As part of a "getting back into embedded development" project, inspired by some round-display ESP32 hardware that really looks like it wants to be a compass, I started updating the map page to experiment with that idea (so that I'd have something to port, once I'd settled on the UI details.)

Betrayal

In the middle of this, I did a google takeout export (the only way to get the contents of a google maps "saved item list" without scraping) and discovered that although the output included links to places and comments, the links were short versions that only had the google-proprietary cid (location uniqueid) and didn't have the latitude, longitude, and zoom level that used to be part of the link.1 Fortunately if you open the link, a redirect and a bunch of javascript leaves you on a new URL that does have the coordinates, so it wasn't too hard to extract updates for this project.

Data Management

Maplibre supports GeoJSON natively, as do a bunch of other things - a far cry from when I worked at MetaCarta and KML was still getting pried out for public use. Since it supports arbitrary metadata and is "web native" it seemed like a fine storage format for my "source of truth" now that google maps itself wasn't suitable (though google maps is still a very convenient search and input mechanism, and remains part of my workflow.)

(I did also experiment with GeoParquet via an in-browser converter - since I've used geoparquet as part of exploring the FourSquare place data - but the convenience of using a text editor on my under-1000-item data set won out.)

Unicode

In the original version of map I directly added every icecream shop as a marker with a popup, which worked fine, and let me just use 🍨 and 🍦 as "text labels" - they're color emoji, but they're also Just Unicode Characters, and that basically Just Works in web browsers in 2025. To use GeoJSON I ended up defining higher level map "layers" and "sources", which is better (more abstract) design… but ended up running into what turned out to be a bug in color emoji handling. I suppose it's a sign that you're really using a project beyond the tutorial level if you're spending time in their Github Issues. Turns out that you can just use imagemagick to fake it with a transparent-background PNG:

convert -pointsize 41 -background none pango:"🍨" icecream-bowl.png

(Since imagemagick is not quite the future, label: didn't Just Work but pango: is "label with a more modern renderer" so that was a reasonable workaround.)

Nearest Ice Cream

The point of the "ice cream compass" was to give you directional pointers to the three nearest ice cream shops. I expected that I'd just measure the euclidean distance to all of the points and then pick the top three, and then optimize it for speed, using something clever with Delaunay Triangulation or Voronoi Diagrams. After a little reasearch and some experimentation, it turned out that

I did the first naïve implementation using .reduce to just get the closest single point; as a Python developer, the obvious generalization to "3 nearest" was to use a heapq (Priority Queue) to efficiently grab the top ones. Turns out Javascript doesn't have that built in - although there are some popular implementations on npm, it's apparently a popular Javscript interview question, and really I didn't need that much of it so I just threw together a "keep the best N" class that used a sort-style compareFn. Simple, easy to review, and still not observably slow (but at least it isn't duplicating the entire data set in memory.)

(Knowing what I know now about browser Javascript performance, I'm not sure there's a justification for anything more than "get all the distances into an array, sort it, slice out the top N", but it was decent Javascript language practice, and more importantly chrome browser DevTools debugging practice.)

The truly important result of the browser prototype was realizing that, since I've already reviewed most of the nearby places, the compass should actually show me the nearest three places I haven't reviewed - since that's in the metadata, it's a trivial filter (which is now a checkbox option on the web page.)

Distance

To focus on building the interface, I just used euclidian distance in lat-long coordinate space - but I've spent most of my life around 45°N and I know that's "useful but untrue". Turns out there's a decent shortcut to a correct distance called FCC's formula which is considered "good enough under 295 miles" - it's not just a great circle calculation, it includes recognition that the earth is ellipsoidal. The Wikipedia version of the equations appears to have been visited by mathematicians, but if you look at 47 CFR Ch. I § 73.209, the upstream Federal Regulation, it's a lot easier to work with and turn into 8 lines of Javascript arithmetic.2

The current implementation does all the work in euclidian but displays the FCC distance for the top 3, mostly to show that the original approximation was really Just Fine, but I'll probably rewrite it to use the FCC version for the math as well.

Conclusion

Though I spent more time on this than was probably reasonable, I did learn a bunch of Javascript and this will enable other projects. It also inspired me to actually go and find more ice cream places and do more reviews and you should go get some ice cream too!


  1. Yes, I can imagine sensible reasons to do this - it makes the output shorter, it allows small adjustments like user-contributed "where is the entrance really" to reflect in saved locations - but it was unannounced/unmentioned and I'd used it as part of my workflow, so I'm comfortable with the hyperbole. 

  2. This also led me to stumble upon Math.hypot which simplified the pure-euclidian version of the code too.