My script to auto-delete Google Maps reviews

Google Maps like much of the web has devolved into an AI generated slurry.

Nowadays every review I leave gets a reply “from” the business which is clearly generated by a machine. All the photos are inevitably going in to train Gemini. And the level-up gamification was fun at first but led to nothing more than endless grinding to reach the next level.

Anyway, I’m out. Have been for a little while. But I’ve left a trail of photos and digital detritus I’d rather clean up.

Google doesn’t have a way to bulk-delete your stuff (for obvious if annoying reasons) so I thought I’d write a little script to do it for me.

We're in Google Maps and there's an automation running to delete photos from the Local Guide section.

The scripts

There’s two scripts, one for photos and one for reviews. They’re pretty naive and need to be restarted from time to time but they should be safe enough.

Huge disclaimer: these will probably get out of date at some point and may not work. You should read and understand the code before you do anything with it.

For photos, make sure you’re on the “Photos” tab of the My Contribution section (see above), then I ran the following in the console:

(async () => {
  const sleep = (time) => new Promise(resolve => setTimeout(resolve,time));
  const go = async () => {
    // click the kebab
    document.querySelector('button[jsaction*="pane.photo.actionMenu"]:not([aria-hidden="true"])').click();
    await sleep(200);
    
    // click the delete menu item
    document.querySelector('#action-menu div[role="menuitemradio"]').click();
    await sleep(200);
    
    // click the "yes I'm sure" button
    document.querySelector('div[aria-label="Delete this photo?"] button+button,div[aria-label="Delete this video?"] button+button').click();
    await sleep(1500);
    
    // check if there's any left, and do it all again
    if(document.querySelector('button[jsaction*="pane.photo.actionMenu"]:not([aria-hidden="true"])')) go();
  }
  go();
})()

And for reviews on the reviews tab:

// delete reviews from Google Maps
(async () => {
  const sleep = (time) => new Promise(resolve => setTimeout(resolve,time));
  const go = async () => {
    // click the kebab
    document.querySelector('button[jsaction*="review.actionMenu"]:not([aria-hidden="true"])').click();
    await sleep(300);
    
    // find the delete review menu item
    const deleteMenuItem = document.querySelector('#action-menu div[role="menuitemradio"]+div');
    if(deleteMenuItem.textContent.trim() !== 'Delete review') return console.error('wrong menu item', deleteMenuItem.textContent);
    deleteMenuItem.click();
    await sleep(300);
    
    // click the "yes I'm sure" button
    document.querySelector('div[aria-label="Delete this review?"] button+button').click();
    await sleep(2000);
    
    // check if there's any left, and do it all again
    if(document.querySelector('button[jsaction*="review.actionMenu"]:not([aria-hidden="true"])')) go();
  }
  go();
})();

These are also on Github because my blog code formatting isn’t great. I should fix that up sometime.


What I learned hacking Google Maps (lol)

This code is pretty naive, and breaks a lot. I had to go back in a few times to restart the script, or adjust the timings when something broke. But it got there in the end.

I do appreciate the simplicity of the sleep() function/promiseified setTimeout. This isn’t in the language because it’s generally better to attach some kind of event or observer or do some polling to make sure the app is in the correct state. But in this case I thought it was a fairly elegant way to hack together a script in 5 minutes.

I could make this faster and more stable by implementing some kind of waitUntil(selector) function to await the presence of the menu/dialog in the DOM. But I would also need a waitUntilNot(selector) to wait for the deletion to finish. In any case that’s more complex, and we don’t need to overengineer this.

Anyway, the second script is done now and all my photos and reviews are gone. I’m still somehow a level 6 local guide (down from a level 7) so good for me.

Screenshots from Google Maps: you haven't written any reviews yet, add your photos to Google Maps + Ash is a Local Guide level 6

Human made web button

I’ve been thinking a lot about the problem of garbage AI generated content on the web. It’s a problem that erodes trust and makes everyone look bad. So how do you combat that?

Web buttons, of course!

A bunch of old school 88x31 web buttons (advertising netscape, IE, Acrobat reader, etc), with my new one with rainbow text "Human Made" and a cute pixel art android with a red strikethrough
I took a minute to make a colourful web button, I hope you like it.

These were popular in the early 2000s as a way of adding pieces of flair to your website, to link to friends, and to show off your W3 validated XHTML and CSS.

A web badge that says Valid XHTML 1.0 A web badge that says Valid CSS

There’s a few archives around showing these off, including The 88×31 GIF Collection and this collection of old buttons. (edit: also Pixel Sea by Daniel aka Melon is a delightful way to explore buttons)

And given my web site is of a certain vintage I thought I’d do something up in the style.

I think it worked out really well. The little robot is very cute, and the colours were kind of inspired by the old Amiga button. And as was common at the time I exported it and closed before remembering to save my sources, so this is it now.


If you want to download and use the button on your own website, go for it. It’s all yours. Use it as you wish:

A cute android crossed out, and rainbow text reads 'human made'.   A cute android crossed out, and rainbow text reads 'human made'.

I’ve decided to upscale it here for effect, but the original version is indeed 88×31 pixels.

If you want to upscale it, you can add the css image-rendering:pixelated; to make it render pixel perfect, rather than using a more blurry modern scaling designed for photos.

Dev log: Debugging Safari, an ogre with layers

We’re releasing a WebGL feature soon, and let me tell you Safari has lived up to its reputation as the new Internet Explorer.

We’ve had two big issues:

  1. Safari completely crashes the tab with “a problem repeatedly occurred”
  2. Safari WebGL rendering flickers when scrolling the page

Safari completely crashes the tab with “a problem repeatedly occurred”

The most concerning issue was the Safari crash, which only happened on iOS, not the simulator. I don’t have any iOS devices to test with, so I made the decision to get myself an iPad mini. It’s a write off!

Anyway I’m now the proud owner of a cute lil purple iPad and it still crashes so that’s a good thing, now I can work out how to fix it.

After debugging for way too long, I worked out this was a memory usage issue. Even though Safari runs on some of the most powerful mobile hardware around, it has a hard limit on how much RAM a web page can consume. Even when that page is open. So you can’t make full use of the device in Safari.

My problem had several causes:

  1. I was loading multiple WebGL interactives on page load. Deferring these until the user scrolls them into view helped fix the issue.
  2. I was caching WebGL textures to make the interactive feel snappier. In the end I had to remove all the caching optimisations to get under the Safari memory limit, and now each texture is rendered in realtime when it’s needed.
  3. On top of this, I was hitting an issue with too many layers in Safari causing excessive memory usage.

Compositing layers are created when animation happens in your page. Much like old cel animated films, the browser keeps content that isn’t likely to change in separate layers so it can sandwich animated layers in between, without having to draw everything all over again. For instance a parallax effect will have a background layer and a foreground layer, moving at different speeds.

A pink panther cel animation, showing the panther being lifted off the background.
An example of animation layers drawn on old school cel sheets. The pink panther can be added and removed from the scene without redrawing the layers underneath. Via The Art Professor.

Layers in Safari are created by a number of things, including:

  1. 3d transforms – e.g. -webkit-transform: translateZ(0);
  2. the will-change property – (intended to be used as a last resort. It should not be used to anticipate performance problems)
  3. Canvas layers – canvas is designed to be drawn and redrawn at arbitrary times, so the browser keeps it on its own layer.
  4. position:sticky/position:fixed – similar to parallax effects, these are just a part of doing business and we can’t optimise them any further

In our case, we had a number of 3d transforms and unnecessary will-change properties creating extra layers. These were contributing to Safari crashing. Cutting down on these layers stopped our page from crashing.


Safari WebGL rendering flickers when scrolling the page

This one was killing me because I couldn’t reproduce it on the iPad, and I don’t have an iPhone to test on.

This mostly seemed to happen in in-app browsers (like Slack), but this morning a colleague was able to reliably reproduce it in Safari proper, and I was able to reproduce it in the simulator.

It only seemed to happen while scrolling text boxes over the WebGL canvas So my assumption was that something was clearing the canvas without drawing the scene back in. The app is creating a separate offscreen WebGL instance for performing calculations and I assumed it might be some sort of weird race condition.

I tried a number of fixes that didn’t help:

  1. Render every frame, regardless of whether render is needed (i.e. don’t pause rendering when the scene hasn’t changed. Based on this similar bug).
  2. I tried enabling preserveDrawingBuffer in the renderer, because it seemed related. No dice.
  3. Disable anti-aliasing (per this bug, where Mapbox GL and Three.js fight it out in the same WebGL context)
  4. Downgrade to WebGL 1 (instead of 2). I can’t find the original post suggesting this, but it didn’t do anything.

The actual bug in my case was completely unrelated to Three.js.

When the screen resizes, I update the canvas size, the camera aspect, and projection matrix so that the canvas scales to fit its new dimensions:

  resizeToRoot() {
    const rect = this.root.getBoundingClientRect();
    this.renderer.setSize(rect.width, rect.height);
    this.camera.aspect = rect.width / rect.height;
    this.camera.updateProjectionMatrix();
  }

The problem is that this code doesn’t rerender the scene. So after it’s run the frame is left blank until the next requestAnimationFrame runs.

This wasn’t a huge problem, except when the Safari chrome disappears off the page the browser triggers a whole bunch of resizes in rapid succession. These were resizing the scene, and resulted in black frames until the scene rerendered, multiple times per second. And it didn’t happen on the iPad because the chrome never disappears offscreen.

Adding a this.renderer.render() to the resize function was a somewhat inefficient but effective fix.

Changelog 2023-26 – a summer christmas

It's a trifle, heavy on the jelly, full of sponge and custard and christmas colours. It's messy but it's 100% delicious.

I have two weeks off so I’m spending the giftmas period with my parents. It’s hot, and I’m sitting on the veranda while Charlie pesters me to throw the ball. There’s a decent breeze, but it looks like we’ve missed the storms.

  • Dark mode fix: I fixed a bug on the site where it was always defaulting to dark mode. It’s been that way since I first implemented it because I was checking the truthiness of window.matchMedia("(prefers-color-scheme:dark)") instead of the boolean window.matchMedia("(prefers-color-scheme:dark)").matches. It should now respect your system preferences, and you can use the little icon in the bottom right to toggle between them.
  • Missing images: The backend of this site is running on WordPress, and there was a period of time where I was using it to photoblog. Anyway, some of those posts only contain a featured image and nothing else, and the featured image wasn’t showing up on this frontend site. So now feature images will show unless they’re already elsewhere in the post.
  • Dessert upgrade: I have acquired my mum’s family trifle recipe. It’s not beautiful, but it is my favourite. We made it together on xmas eve, and I’m so happy with it.
  • Hardware acquisition: I placed a late night ebay bid on an old Thinkpad and put Arch Linux on it, inspired by Josh‘s recent blogging. It’s a lot of fun and even though it’s a few years old, it’s way faster for day to day tasks than my M1 mac. Obvs not for video work, but I want to use it to get back into vector art. Though I see the Arch community has AURs for DaVinci Resolve which is shockingly difficult to get running on Linux, so I might dust off my desktop and put it on that as well.

Beyond that I made rum balls in my parents Thermomix and they came out alright. I blitzed up some almonds for a bit of health, and I think I’m gonna start making protein balls at home as a healthy snack. Do you have any good recipes?

I’ve also booked accommodation for my second week of holiday so I’m gonna take my new bike for a spin out to Bribie Island for a few days. No vlog cos it’s gonna be public holiday and kids everywhere. But I’ll post some bits on Mastodon.

Test run across the bridge, it’s _so_ nice to ride. I missed having gears 😆

#breadposting: the story so far

A loaf of delicious golden milk bread, with a freshly made ham cheese and tomato sandwich in the foreground. It looks succulent.

Lately I’ve been making bread.

I know people have been on this bandwagon since the start of the panini, but by bread making adventures are more out of necessity.

I like bread. But I’m not a big sourdough man. I want something light and fluffy to put cheese and cold cuts and salad on. Maybe a little peanut butter. But living in a gentrified neighbourhood, the options are stark.

The Aldi down the road is abominable. Bread from Aldi is made off-site before being shipped in, and in my experience it’s stale before it’s even put on the shelves.

The cheap Woolies bread is decent, but the store has staffing issues. Recently the sole baker went on leave. I don’t begrudge them that, but it meant there was no fresh bread on the shelves, and it had a flow-on effect to other supermarkets nearby. Fresh bread became impossible to get!

Then there’s the French patisserie around the corner. I’ve tried their baguette a few times and it’s alright, but it’s crusty enough to cut your teeth on. I don’t like bread that leaves sore bits in your mouth for the rest of the day so it’s really only an occasional thing.

So at my wit’s end I’m making fresh bread.


Japanese Milk Bread

It started with a message from Ben (as it often does):

Milk bread is not the easiest bread, especially to start with, so I’m told. But I was sitting around at my parents’ place with an idle Thermomix and little else to do so I figured I’d give it a shot.

The tricky part of milk bread is the tangzhong, which is a paste of cooked flour and milk. Wikipedia says it improves the texture of the bread, and also “stabilizes the wheat starches in the bread, to prevent recrystallization” which stops it going stale.

The recipe had Thermomix instructions for the first steps. I have a bit of a love-hate relationship with the Thermie because the recipe paywall stuff is a nuisance when trying to find a recipe, and it’s kinda hard to deviate from a set plan. But this one was a custom, non-wizard affair so it was fairly easy to follow. The trickiest part was finding the “dough” setting — apparently it just spins the blades backwards rather than having a specialised dough hook.

As it was my first time making bread, and it seemed to take forever, because I was hanging on every step of the recipe. Peering at the Thermie waiting for the temperature to get just so. Waiting around for the bread to prove. I didn’t want to leave it, in case it grew legs and ran off.

But the results were great. The bread was true to its tangzhong: soft and fluffy and kind of set my bar high for the rest of my bread making career.

A loaf of perfect bread. It's got three domes, and is perfectly golden brown.

Making bread with a Ninja

At home I don’t have a Thermomix, but I do have a Ninja Foodi Power Blender and Processor System which comes with a dough hook. So I got myself a loaf tin, and tried to recreate the magic.

The Ninja was a bit fiddly on the dough setting. The program runs for exactly one minute and it rattled all over the bench so I needed to hold it down. It seemed super quick for a bread loaf so I ran it again and it went into some kind of shutdown protection mode, presumably because the motor was working too hard.

I later found out you’re supposed to manually break the dough up before running it again, which helps the food processor process it. But even with one and a half runs, the bread came out super impressive for a second attempt.

A very thick cut sandwich, with chicken, tomato, cheese and greens.
I don’t know about you, but a thick-cut milk bread grilled chicken burger is exactly the thing I want.

Soft white bread is too hard

Having mastered the milk bread, and truth be told gotten a little sick of it, I turned my attention toward white bread.

And so I set out confidently on my next bake. Little did I know it would be my undoing.

I don’t know exactly what went wrong, but it was probably a combo of:

  1. I got scared of the Ninja dough setting burning the motor out, so I only did one run of the kneading. That’s 60 seconds, which is probably much too short.
  2. It’s summer, and we’ve been in a heatwave so the temperature has been pretty high and they may have proved too fast.
  3. I think I probably let them prove too long, because:

They immediately deflated in the oven. The first attempt came out like a long flat bread brick. It was edible, but highly questionable.

The bread is maybe 5cm high, if that. It looks dense, and unappealing. Looks a bit like it could knock you out.

The second time I tried to prove in the fridge to try to account for the heat variable. It didn’t seem to do much.

It didn’t really rise much in the fridge overnight, I thought it was supposed to? But it’s currently warming up again on the bench. I’m determined to make this work.

And to make matters worse, I took it out of the oven too soon so it was still raw in some parts. I’m not even going to show that one because it was a disaster.

Thankfully Shawn was here to help eat the edible bits. And it was tasty! But a fair bit of it went in the bin.

So even though that was only yesterday I thought I’d give it one more try. This time I:

  1. Ran the dough through the food processor several times. It came out a much better, springy consistency.
  2. Didn’t fuck around while proofing. I let it go for 30 minutes the first time, and another 40 minutes the second time.
  3. Gave it a bit of a bash to make sure it wasn’t going to collapse before putting it in the oven. It stayed solid.
  4. Got overly excited and underbaked the damn thing again lol.

So I sit here staring at my still slightly-flat loaf cooling on the rack. Apparently allowing the bread to come to room temperature rather than tucking right in “optimizes texture and flavour, and there are several theories as to why“. So I’m gonna leave it go.


Several hours later

I cut the bread and to my surprise it actually looks like bread.

It’s still a touch heavy, but the texture is about right. One jam and peanut butter sandwich later I’m content.

Two slices of bread are cut off. It's a fairly good crumb, with an even texture. It's about 70 cm high, and there's peanut butter and jam in the background.

I still don’t entirely know what went wrong with the others, but the extra kneading and possibly the quicker proving time made a difference. Next time I’ll just have to keep it in long enough for a bit more colour. And I think if I want it to be any taller I might need to multiply the recipe.

I’m not sure how to get it fluffier but apparently there are various types of tangzhong that can help get more moisture in, so I might try that next.

Anyway if you have any ideas, hit me up. I’m keen to hear your tips and tricks. You can reply to this thread on Mastodon or just shoot me an email.

Gonna try Vodafone this year & I got a ticket

I now have the trifecta of wifi at home, work, and my parents place. So I just ordered the cheapest $100/year Kogan/Vodafone SIM and I’ll activate it when my Telstra prepaid runs out next month. You can’t beat those kinds of dollars.

I’ve been at my parents all week for assorted reasons. Just finished the drive home.

I got pulled over by a police man on a motorcycle on the way back and given a fine because my bike was obscuring the number plate. He didn’t want me to fix it or anything, just a fine. So that’s cool. That’s $125 bucks going towards… I don’t know, what does that even contribute to?

I’m exhausted. It feels like perpetual Sundays afternoon.

Some of my plants are dead, some of them are double the size.

I’ve collapsed on the couch, I fear I may never get up again.

Things are alright.

First weekend of spring

On Saturday I woke up with a vague sense of unease and decided to fix it by cleaning the house. Kitchen, laundry, bedroom, floors. I didn’t have anywhere to be so as I noticed tasks I did them, and it left my place feeling like a great space to be.

It’s been feeling like spring for most of winter, it’s been a very mild one. But nevertheless the sun has swung around and I’m getting more light in my place now. I can leave my doors open (screens closed) to let air circulate, and the plants are having a great time.

It is a time of optimism and, well, occasionally having to put that climate anxiety back on the shelf. But largely optimism.

Riverfire 2023

I didn’t get up to much during the day but in the evening I decided to head out to watch the Riverfire fireworks.

It’s usually packed in South Bank and everywhere really, so I though I’d roll around on my bike and find a spot. Turns out the Kurilpa Bridge was open and you could just hang out and watch from there. I didn’t have an amazing view, but it was a nice $0 activity that took almost no effort on my part.

On the way home I stopped at the William Jolly Bridge because there’s a light installation underneath now. That night it was lit in rainbow colours and looked fetching in the eerie firework smoke.

Sunday funday with these idiots

Ben came over and we went to Banette, the little French bakery.

They revamped their menu and they’re providing table service now so it’s a proper place to dine out. I was really happy to see their sad and crusty Croque Monsieur from the hot box is now freshly made, along with a number of other breakfasty options.

So we had brunch for lunch. It was pretty great. I don’t imagine I’ll get this too often, but it was a decadent but not too heavy option and the greenery really rounded it out. I’m very pleased.

Mrs Crunchy with a fried egg and salad on a plate

After that we went on a nursery crawl and looked at all the plants.

I’m trying to be a bit frugal since I changed jobs and accidentally went 6 weeks between proper pay cheques, so I only picked up a couple of little friends and a colourful pot because I need to re-pot one of my plants.

But Mappins also has fish and they were very cute and I really wanted to take one home with me. That might be a separate project needing a bit more research, but I reckon it could be a good hobby.

A brightly coloured pot with a couple of grean leafies inside.

We spent the long weekend in Coffs Harbour

Shawn stayed at my place the night before, so we could wake up and leave first thing. The sun came up as we drove across the border to NSW, spreading sunbeams across the farmland and leaving misty gulleys where the light didn’t reach. The mountains were beautiful, but truth be told there wasn’t much to see from the highway.

We arrived in Coffs at about 9AM and made a beeline to what turned out to be the greasiest, fingerprint-covered Maccas I’ve ever been in. We got McMuffins and headed down the road to the closest beach we could find.


Overcast with patches of blue sky over Park Beach. Muttonbird Island in the distance.

I didn’t bring swimwear or a towel because the forecast was for overcast and rain all weekend. To be fair, there was a little bit of cloud when we arrived, but it cleared out soon enough to reveal blue skies and a high UV index that continued for the entire trip.

After McMuffins, sunscreen, going back to the car to fetch hats, and sitting on the beach watching the crabs for 53 minutes (according to my Google location history), we set off on a walk.

Coffs surprised me with the amount of walkways and free things to do. I’d only ever passed it from the train. It looked like a nice beach and a handful of banana plantations and that’s it. But the coastline is full of parkland and beaches, and all linked together with a nice wide path that extended further than we were willing to walk.

A concrete and steel bridge spans a tea stained creek, the beach in the distance.
This is the rail bridge where you see the coast for the first time, on the Brisbane to Sydney train. It’s ugly but I like it a lot.

We managed a couple of kilometres to the marina, and poked around the artificial breakwater before reaching Muttonbird Island

The island is attached to the mainland by the marina these days, but is still an important nesting ground for the little birds to dig holes in the ground and raise their chicks. There’s a designated walkway from one end of the island to the other, and signs warning you not to stray, lest you step on a sleepy bird. Or break an ankle.

Panoramic view from Muttonbird Island, over the harbour and mountains beyond

We didn’t see any birds, I think we were a bit late in the season. But I appreciated the cliffs, and sat mesmerised by the overwhelming power of the ocean smashing against the rocks. And there was a pretty cool ocean bird hovering in the air for most of the time we were climbing the island again to get back to dry land. (Black wingtips, not sure what it was.)

Our inappropriate choice of clothes made for an uncomfortable walk, so we started back to the car to check into the hotel. But we stopped at the surf lifesaving club for lunch first. I had a parmi and Shawn had a chowder.

I saw they had Schweppes behind the bar so I asked if they had Pepsi Max, and the man said no, but the other girl said she prefers Pepsi Max, and the man asked if there’s a difference and we both said YES! and he said maybe they should switch from Coke No Sugar.

This conversation continued every time I went to the bar, and also when he came around at closing time (2:30) to pack up the tables. We had a rapport.


Check-in was easy enough. The place was 90s vintage, but pretty clean and had an amazing view of the ocean, islands, heritage lighthouse etc. The lighthouse came to be my main wayfinding point throughout the trip.

The window between the bedroom and the toilet was an unusual choice, but we devised a roster system to prevent surprises.

That afternoon we sat on the balcony and ate far too much, watching the myriad of bird life screech and warble and do its thing. There were two tourist planes as best I could tell; one dropping off paragliders, and the other one just hanging around in the sky soaking up the sights.

The view from the hotel balcony, there are some other holiday units, but we're up high and can see the ocean over the top of them. There are two islands jutting out of the water.

The main gotcha was the lack of wi-fi. Despite the blinking modem on the counter, the lady at the front counter said it stopped working and they removed it from the listing. And lo, turns out I’d booked a place without internet.

That night we tethered to my one bar of Telstra and watched Miriam Margolyes reluctantly trudge around Australia and be grumpy in standard definition.


We decided we didn’t need to spend a whole bunch of money on holiday, so we stocked up on supplies and made sausage & egg muffins again for brekky.

We took them to Moonee Beach and grabbed hot chocolates from the cute coffee shack by the beach called I Bean There, and sat watching the surfers, dogs, and hilariously the kids trying to ride their bikes through the sandy creek.

I spilled chocolate all down my nice white shirt, but it mostly came out with wet wipes.


After brekky we drove up to Korora Lookout. It’s a public access lookout with indigenous significance. There’s a little pedestal to read aloud the story of the area, and a cunty next door neighbour who’s planted bamboo along the edge of their property to stop people enjoying the view.

This was where I saw my first leech: a little inchworm-looking thing attached to the sole of my shoe. I knocked it off, and we both hiked our socks.

We walked to the second lookout, with a purpose built suspension deck that some dude thought would be funny to make swing while we were all standing on it. The views were amazing and I tried (but failed) to spot the train line into town.

A viewing suspension viewing platform juts out over the raingforest, with views of the town and the ocean beyond.

We stayed for a moment before setting off on a bushwalk into what I refer to as leech gully (side note, that’s what Indooroopilly means. It’s a corruption of the Yuggera language.)


We started trekking down the hill in a nice moist rainforest setting. We were planning to take the trail a couple of kilometres out, but as we descended and the path got thicker with foliage. The sunlight disappeared. Fern fronds brushed past our legs.

That’s when we saw them.

Mother and two kids making their way out of the forest, looking stricken.

“Turn back!” she said, with fear in her voice.

“The leeches have leeches!”

One of the kids had blood streaming down his leg.

We tried to make it to the next clearing, but I freaked out. Shawn wanted to go ahead, but I couldn’t enjoy a hike when there’s blood sucking parasites waiting to hitch a ride.

We turned back and took the shorter, better maintained path in a little loop back to the lookout.


From there we drove to Sawtell, stopping at a roadside stall to buy bananas, and enjoyed ate them at the lookout.

A friendly family of magpies came to us to beg for food so I threw them bits of banana peel and they seemed to enjoy it. The seagulls that showed up shortly after didn’t care for it at all and were FURIOUS we didn’t have chips.

There was a little path down to the beaches, but we only went about halfway and sat at a cute bench, watching the ocean and avoiding the walk back up the hill to the car.

A man sits by himself at a bench overlooking the ocean, on the side of a steep hill, with a paved path trailing into the distance.

The next day was a public holiday in Queensland, and I was scheduled to leave on the train to Sydney.

Shawn made up the last of our muffins while I showered and went to the jetty for coffee and a bit of sunlight. There were plenty of cute dogs and little regional planes to spot. But eventually it was time to head to the station.

I’d booked a first class ticket from Coffs to Sydney. Usually I book two, because I’m large, but I figured for the short journey I’d be fine. But a few hours into the trip one of the rail staff started hovering around, awkwardly looking at me.

I popped out an earbud.

“I don’t think I have to tell you you’re a big guy,” she says sheepishly.

“There’s another passenger getting on who’s seated next to you. So I’ve moved you a few seats back to your own seat.”

“Lol,” I laughed.

I swapped seats and the rest of the journey was pleasant, but uneventful.

The XPT train pulls into Coffs Harbour station, painted in yellow white and blue.

How I rolled my own vector map tiles

OpenStreetMap is like the Wikipedia of maps. Back in the earlier days I used to love running around gathering data and mapping every neighbourhood I could.

I reckon I contributed a pretty big portion of street names on the north side of Brissie, by riding around on my bike with my Nokia 6120c (great phone!) and a bluetooth GPS dongle, recording all the points of interest like a pro, to upload to the map when I got home.

It was a great hobby at the time, when vast swathes of Australia were completely blank. Now OpenStreetMap is pretty feature complete, it’s used everywhere.


A short history of maps as a web developer

Back in those days the state of the art for web mapping was the tile-based “Slippy Map”.

Everyone used it, even Google Maps. You’d essentially have a Javascript frontend to let visitors zoom and scroll the map like you do today. But on the server a process would convert all the OpenStreetMap geodata into standardised image tiles (raster tiles).

Tiles were commonly created at 256×256 pixels, and were rendered at zoom levels from 0 (the whole world in one tile) down to zoom level 19 where the world would take up 274.9 billion tiles.

A map of Australia and surrounding nations, split into a 256 pixel grid

This was generally an on-demand process as rendering so many tiles would be infeasible. Ridiculous. Absurd. I can tell you this because I tried a couple of times. Not for the whole world, but a few times I’d tried to scrape, render, cache the entire of Brisbane for assorted projects.


Eventually Mapbox came along with an easy-to-use interface on top of the open source data, and reasonable enough pricing to make it worth switching over.

I gave a talk a decade ago about the cool stuff people were doing with maps, and that included plenty of Mapbox evangelism.

Later Mapbox standardised the Mapbox vector tile format which had a lot of benefits over the older raster tiles.While a raster tile could be styled to look however you want on the server, a vector tile could be styled on the client-side. That meant the same tile could power a hundred different map styles, even dynamically on the client-side. In addition, vector data makes things like animating between zoom levels look great. Generally, a huge step forward.

The new OpenGL map library was released to take advantage of these benefits and it unlocked a lot of really high quality maps for the masses.

By this point high quality maps were par for the course and radical innovation in the space kind of flattened out.

My opinion of Mapbox turned when they went the way of every venture backed startup; got involved in union busting, closed-sourced their tools and started turning the money dial up. 

That’s when I started playing with maps again.


Cycling maps

Since at least 2010 I’ve maintained briscycle.com in some form or another, and always one of the main features has been maps to show safe routes and how infrastructure connects up.

I’ve gone through phases of running my own tile server, using statically rendered tiles, and third party map services including Mapbox (who can’t do very good cycling maps fyi). But recently I figured I’d go back to rendering my own.

I don’t remember where I spotted tilemaker, but it has such a sweet looking website that it inspired me to have a go at building my own vector tiles. It wasn’t as easy as the website led me to believe, but after lots of trial and error, some coding in lua to get the right properties out, I managed to get a decent looking cycling map out of it.

A map of Brisbane. It's fairly desaturated, except for the green cycleways and bike lanes everywhere.

I largely followed the instructions from Wouter van Kleunen’s how-to blog post, then:

  1. extended it by customising the lua processor to pull out more cycling attributes (and skip attributes I wasn’t interested in.
  2. styled the map using a standard json map style, but I also processed that on the client-side to add more repetitive things like road casings. You can check out the code here. (edit 2024: apparently maputnik lets you create style json in a graphical way)
  3. Set up a small Docker machine to serve mbtiles (dockerfile source)

The result is pretty cool.

It’s very fast because it’s hosted in Brisbane for a Brisbane audience, so the map tiles don’t need to transit the globe before being displayed.

The tiles themselves are optimised pretty well and allow me to tweak the styles in almost real time. There’s still a few weird bits, but I reckon it’s a good base layer to add stuff to, like geojson routes (check out the brisbane valley rail trail).

So that’s it from me. You can check out the map at briscycle.com/map or check out some of the cycling trips in Brisbane for more.