bdon.org

First ATProto bot: every map #

I made my first ATProto bot: bsky.app/profile/latest.allmaps.org.

Inspired by Every Color and Every Lot-style bots, it finds a recent historical map from latest.allmaps.org, composites the warped map with present-day OpenStreetMap features, and posts the image to the Bluesky network.

Some recent discoveries by the Allmaps Latest bot:

ATProto

ATProto is not Bluesky, but Bluesky leans heavily into it as part of their marketing. ATProto is the most contradictory and exciting part of Bluesky so far:

  • It enables other microblogging networks to compete directly with Bluesky using ATProto, albeit without Bluesky’s brand goodwill or proprietary feeds.

  • it differentiates Bluesky from being simply a clone of Twitter from 2019, which is a product interesting to me, but not a product interesting to venture capitalists.

  • It is stupidly simple to write a bot. Automating Twitter accounts required multiple auth and approval hoops to jump through; posting 1-megabyte blobs to the Bluesky PDS is so easy that I expect it to be locked down soon.

...
import { BskyAgent } from '@atproto/api';
await agent.login({
  identifier: BSKY_USERNAME,
  password: BSKY_PASSWORD,
});
const uploadResponse = await agent.uploadBlob(image, {
  encoding: 'image/jpeg',
});
...

Allmaps

Allmaps is a set of tools for working with georeferenced maps in the IIIF specification.

Georeferencing is a fancy term for warping a paper map to match the geography of the present-day real world, so you can composite the image on a “slippy map”.

The Allmaps project has adopted Protomaps for some base layers of the present-day map. This is part of Allmap’s value proposition as a open source toolkit and not a centralized platform. Source IIIF images are hosted at institutions like the University of Chicago Libraries or the National Library of Scotland, and the Allmaps tools consume those images directly via the IIIF protocol.

Each institution must be able to run the entire Allmaps stack themselves. It would be prohibitively expensive and inflexible to require, say, a Google Maps API key for every Allmaps deployment, so a single PMTiles archive is the simplest alternative.

The Allmaps Latest bot runs on Cloudflare Workers and uses kanahiro/chiitiler (itself a frontend to MapLibre Native) to composite the map from Allmaps APIs + Protomaps basemap .pmtiles build.

protocol : platform
pmtiles  : protomaps
iiif     : allmaps
atproto  : bluesky

I am lacking a PhD in JavaScript packaging #

As of now all of the Protomaps front-end packages produce 3 output formats:

  • CJS or “CommonJS” which is used by NodeJS command line and server programs.
  • ESM or “ECMAScript Module” which is adopted across all “front-end” tooling but still an experimental opt-in for node.
  • IIFE or “Immediately Invoked Function Expression” which is a convention and not a standard. This makes loading the library possible via a single script tag in a page header, and populates a single global variable e.g. pmtiles.

It is 2024 but making a bit of JS/TS code and publishing it to be widely useful is harder than before instead of easier.

It is possible to load ESM natively in browsers, but complex apps like a web map have dozens of internal dependencies, and would need to fetch dozens of individual module files. And, developers expect to author in TypeScript instead of raw JS.

So the Protomaps libraries all use esbuild for transpiling to 3 output formats, via a wrapper tsup (thanks Ben for the tip).

For publishing web maps, there’s a diverse range of front-end rendering libraries and developer preferences:

Leaflet

The lightweight Leaflet library works really well if you prefer build-free JavaScript. The quick start loads Leaflet from unpkg:

 <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
     integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
     crossorigin=""></script>
 <script>
 	var map = L.map('map').setView([51.505, -0.09], 13);
 	...

If you are using Leaflet in this way you will want to refer to pmtiles via the IIFE build on a CDN or your own host:

<script src="https://unpkg.com/pmtiles@4.0.1/dist/pmtiles.js"></script>

OpenLayers

OpenLayers takes the opposite path: the quick start tells you to create a NPM project with a bundler:

npm create ol-app my-app

it is possible to load core OL via a single script-includes like I do in this example but it will download a large bundled file (likely due to OL’s many features); you will instead want to build your own bundle from ESM for any production project.

npm install ol-pmtiles will let you use the ESM build (in most bundlers) and access TypeScript types, etc.

MapLibre GL

MapLibre works just fine wither either script tags or bundlers:

MapLibre script tag example

or npm install pmtiles alongside npm install maplibre-gl in your project, like in the protomaps docs repo.

Other

The CJS output exists for NodeJS programs that manipulate PMTiles archives and map styles. For example, the CloudFormation template for deploying a lambda proxy is a single YAML file; this works because you can inline the entire JS code in the YAML, but it only accepts CJS and not ESM!

If you have a suggestion for how to improve this you can find me on Bluesky or Mastodon.