Water Biscuits

I couldn't quite get it ready for my EdinburghJS presentation but I now have water being properly handled in Linzer. It's a subtlety, but I found when exploring in my Garibaldi iteration that you got better regions if you treated water as part of the boundary.

Just to recap, the way I previously defined regions ('biscuits') was:

  1. Find city boundary
  2. Choose random start/end points in that boundary
  3. Find routes between those points, either biased to be pedestrian or car (auto) routes
  4. Draw the city boundary as black and the routes as white borders (see below); this effectively unions the routes
  5. Find connected white pixels and turn back into regions

(note: image is reflected in x-axis)

The changes I made were to steps 2 and 4:

  • 2: Mask-out the water areas from the city bounds so that they can't be used as start/end points (sorry swimmers!):
  • 4: Draw the water areas as border pixels:

This is hugely impactful for somewhere like Amsterdam which is 15% water! (maybe more on that later in a related side-project)

One of the funny things about long-term side-projects is that you get the "opportunity" to come back to short-cuts you'd taken earlier. I was wondering why in places like Paris it wasn't finding regions on the islands in the center of the city. Before adding water I didn't really have to deal with drawing any polygons with holes, which these parts of Paris happen to have. So, it wasn't finding them because ... I hadn't bothered implementing rendering of interiors.

Turns out it was actually fairly easy to implement on top of tiny-skia using a Mask:

let mut mask = Mask::new(pixmap.width() as u32, pixmap.height() as u32).unwrap();
...
let path = path_from_line(poly.exterior())?;
mask.clear();
for interior in poly.interiors() {
    let interior_path = path_from_line(interior)?;
    mask.fill_path(&interior_path, FillRule::EvenOdd, true, transform);
}
mask.invert();
pixmap.fill_path(&path, paint, FillRule::EvenOdd, transform, Some(&mask));

Anyways, it's been really nice to draw a line under something I've known I've needed to do for a looooong time.

Biscuits and Beyond (presentation at EdinburghJS)

Many thanks to EdinburghJS for hosting me at their Geo and JS June Meetup, particularly since I mentioned very little Javascript (zero 😊).

The slides are available, but here's all the links I mentioned:

Linzer: Similar Regions Across Multiple Cities

It was great to get along to my second "Makers gonna make" event at Codebase on Saturday, despite the Red Weather Warning on Friday. Many thanks to Peter Trizuliak for organising.

It's a pretty laid-back environment with a nice mix of focus time, breaks for pizza and free coffee / food (thanks to sponsors, Shopify, Codebase and The Source Coffee roasters). You can present what you've done and/or head to pub later, but no pressure to do either. The next event is coming up in February; I unfortunately can't make this one, but maybe see you at another!



I worked on some extensions and performance tweaks to make my Linzer app work with multiple cities (see previous blog post for some context). I mainly did this to continue providing examples that the Region "Signatures" made sense. Given unrelated stuff I need to work on in Q1 2025, it'll be a while before I have something meaningfully that uses them but it's good to see them making sense so far.

Thanks to all the attendees for feedback, including links to the very interesting-looking Creating with Data (subscribed!).

Linzer: Region Summaries and Similarities

Over the holidays I was thinking about where I am with the Biscuits project. The Linzer version has gotten to the point where I have a lot of the core bits of technical knowledge required, and I "merely" need to understand the aesthetics, if I wanted to copy the original artist.

Whilst it is good to attempt to copy to learn, I am now a bit uneasy with it as a goal. This is partly because the habit of "Computing People" copying "Artistic people" is becoming a bit rude in the current race-to-the-bottom of AI Art. However, it's also because I have some other ideas I wanna explore. So, I'm gonna do that instead.

This doesn't change the need for good core parts. To that end I've been putting together something that I call a "Region Summary". This is intended to be a lossy-but-simple summary of a Region, that still allows comparison between them.

It is defined as:

  1. Find the Centroid
  2. Draw a Line from each Point on the border to it
  3. Find the Bearing and Distance of each of these
  4. Accumulate the Distance against each Bearing and keep the maximum Distance
  5. The Dominant Bearing is the one for which the sum of Distances at 0,90,180,270 relative to the Bearing is maximised
  6. Normalise the (Bearing,Distance) pairs relative to the Dominant Bearing

This is "lossy" because when a shape has nooks, if these end up doubling-back from the pov of the Centroid, then they will be lost. However, it retains more detail than just finding the Convex Hull.

This Summary has some nice properties such as being able to use to find similar Regions. Here is an example where we treat it is a 360-element Vector and find those Regions within a certain "similarity" where similarity = (1.0 - avg(diff between distances at each Bearing)):

This version only has Regions for Edinburgh but you can have a play yourself here: linzer.houseofmoran.io.

This now gives a substrate on top of which I can build other stuff.

However, part of the reason I am writing this up here is because this is almost certainly not a new problem / solution. I wanted to have a go myself to better understand it, but I am very much up for suggestions of alternatives, or even a well-known name for this technique.

(I can be contacted on Mastodon or Bluesky).

The Biscuits Project: decomposing the structure of cities

I did a talk on this project at the GeoMOB Edinburgh event on 1/10/2024. The slides are available, but I'll give a summary of the accompanying explanation content here.

The "Biscuits" project began back in September 2017. It was inspired by a work of art by Armelle Caron called Les Villes Rangees:

I wondered if I could automatically "destruct" and lay out the sections in a similar way. So began a side-project I've been working on, off-and-on, for 7 years. The original Github project gives a bit more detailed history, but the major iterations were:

Speculaas: April 2018

  • 😞 screenshotted static 'maps'
  • 😀 animated and interactive
  • 😀 bin-packing auto-layout

Garibaldi: August 2019

  • 😀 dynamic maps
  • 😀 interactive (runs in browser)
  • 😭 pixel-based region-building affected by resolution quirks

Obviously we then had COVID happen in 2020, which was distracting to say the least. However, the biggest issue was that I discovered doing things entirely in the browser at pixel level was not very robust. There were various techniques I could have applied, like a separate off-screen canvas for calculations. That would require a lot of testing to get right and make work.

It felt though that this was going against-the-grain and, more generally, I was really just reluctant to go learn about how OpenstreetMap and Geo API's / libraries work.

Between then and now that's effectively what I've done, or at least enough to recreate where I got to in Speculaas, albeit entirely off-line.

The current version, which I got ready for the GeoMOB presentation, is pretty simple at the high-level:

    flowchart LR
    b(Boundaries) --> r(Regions) --> l(Layout)
    

Boundaries

This is where the Map data enters. We want to get to a binary version of an area where "black" = part we want to keep and "white" = a border of some kind.

I'll not go into detail here but this image was derived by sampling and overlapping routes across Edinburgh from Stadia Maps.

Find Regions

The area is defined in terms of pixels and not geo coordinates. This is partly because I find it easier to do, but also because it gives me most freedom to mix-and-match sources. So, for example, whilst I've not done it yet it in this version, adding an exclusion area for water is as simple as finding a source and drawing it as white pixels.

The actual Regions are found by applying a Boundary Tracing algorithm at the pixel level.

Back to Geo-land

To get back to Geo-land I re-interpret the boundaries that have been derived into geo Polygons.

Layout

There are lots of different ways layout could be done. For this iteration, as in Speculaas, I treat this as 2d bin-packing problem.

This particular example here shows a layout based on a Guillotine algorithm.

What's next? Well, first a scorecard on this implementation, which I've christened "Linzer":

  • 😀 pedestrian and auto-biased route sources
  • 😀 deterministic
  • 😀 bin-packing auto-layout

After this, there are a lot of things I could do next to improve it. Some of these I've done before in different iterations, and some are totally new:

  • boundaries:
    • roads, paths
    • clip by city boundary
    • mask-out water bodies
  • regions:
    • green areas
    • rotate to minimal bbox
  • layout:
    • layout as text glyphs
  • display:
    • animated placement

However, since this is more of an Art Project than a technical one, I reserve the right to "honour my whimsy" and do something totally different.