this is aaronland

What if web pages “foxed” like paper?

The Shape of Content

This is a small piece. A simple HTTP interface, called ws-raster, to wrap Batik's SVG to PNG transcoding functionality.

Like the ws-decode (2D barcode decoder) endpoint it takes input posted to a URL, turns it in to something else and sends it back. Nothing special here but the ability to expose some code-magic available mostly-only in Java without having to write everything else in Java. I say mostly-only because the Cairo libraries have both support for SVG and bindings for higher-level languages like Python. But the cost of being written in C is a twisty maze of dependencies and rain-dances to install anything which makes distributing Just-Make-It-Run style packages difficult. ws-raster weighs in at a hefty 4MB, compressed, which is a little goofy for such spartan functionality but that's mostly all of the Batik widgets that come with batteries included so that it will Just Work® out of the box.

So, yeah : It takes an SVG file and returns a rasterized version as a PNG file. Why? Pirate maps, of course. A way to take a feature-rich online map and boil it down to a simple set of landmarks and directions, all of which can be easily printed. Consider a popular coffee shop in San Francisco :


The first thing you'll notice is that the tiles in the first and second image suddenly change; switching from Yahoo! Maps to Open Street Maps. (Actually, if you're from San Francisco the first thing you'll notice is that Ritual is really on the other side of Hill Street. So it goes with geocoders. They know the shape of the elephant but if you ask a geocoder where to feed the elephant you may end up sticking the peanut in...well, never mind. The point of these things is to get most of the way there. If you can't figure out the rest then maybe you've been drinking a little too much kool-aid.) But, switching map providers : that supposed to be bad design, I guess.

I did it on purpose to demonstrate that even if you used a tool like del.icio.us maps (or, say, the Yelp API) to find something you might use a separate application to actually make yourself a pirate map. Okay, the truth is you probably don't care one way or the other but just go with it. Imagine being able to link to a completely different pirate map tool simply by passing a location's latitude and longitude. That's the second image which, in turn, uses ws-modestmaps to render the map view for that location.


The biggest problem with the pirate map generator is that it doesn't exist yet. But it would look something like the third image. The idea is to have a simple web application which displays a static map image with an SVG (or Canvas) overlay and controls to draw straight lines and text and move them around. Curves and colours would certainly be nice but seem like overkill right now since the goal is to produce something barely more complicated than the fourth image; this is what ws-raster does. Once the SVG layer has been serialized it can be sent off (in-situ if you want, using the magic of scrumjax) and converted in to a PNG file. Which, it's true, is kind of stupid in a web application.

The point, though, is not necessarily to generate PNG files in the pirate map generator itself. Rather you want to draw the map once and then retrieve it later. For example, when you are aggregating a collection of places to create a printable guide. Some day this might happen in a web browser but right here, right now it's still going to happen in boring-old programming space using something like pmPDF, which is written in PHP. That's what nice about these stupid HTTP wrappers: a POST request is the same in JavaScript as it is in PHP as it in Java and doubles as the glue between all three; I don't have to write another PDF generator in Java or an SVG converter in PHP or either in JavaScript.

There is a second part in all of this left as an exercise to the reader : How and where you store the pirate map, whether it's an SVG file or a PNG image, in the interim? The glib answer is : Just put it on the Internet. The serious answer is : No really. Imagine that the place you've just drawn a map for has some sort of unique identifier even it is just its latitude and longitude. That's your identifier which can be turned in to a fragment in a URL or an attribute, on a file, that can be indexed. It becomes the glue that connects, say, a restaurant listing and a pirate map and a printed document (and then back again using tricks like 2D barcodes).

We are fortunate to be working at a time when network-enabled services for storing blobs of data are readily available, easy to use from a variety of programming and application environments and dirt cheap. You can easily squirt either the image itself or SVG source in to a blogging platform, store on Amazon's S3 servers or abuse any of the various social sharing sites (which is bad form but I mention only as an example since people do it anyway). For all anyone knows you could store the data locally on your hard drive and, using the magic of the file:// URL scheme, none of the other pieces in the process would be any wiser.

Find, plot, identify, draw, store, retrieve, render, print, find.

This is classic plumbing which means you still need to fasten all the pieces together yourself. As a consequence, people who aren't comfortable, or don't care, to work at this level are left standing on side muttering things like dorks under their breath. Another way of looking at that is to say when all there is are are just pipes and valves and connectors it means more people can stick more bits together in more different ways building more actual applications for more people, including the ones who don't care about the details.

That, at least, is the goal. One small piece at a time : ws-raster 0.1

Rainbow extends Pony throws Unicorn

I am a Java programmer. Sort of. I guess. There, I said it.

I have my preferences, for sure, but I try not care too much about one programming language versus another. I was schooled by sysadmins and security weirdos and learned early on that all languages suck and you simply choose whichever one sucks the least for the task at hand.

What I have developed a strong opinion about is documentation. And example code. Maybe the other way around.

For all that people complain about Perl I have yet to see any other community as committed to and as diligent about documentation. I've given up trying to understand why people are so resistent to Perl's Plain Old Documentation format. I suppose some of the rules are confusing (in the same way that 10% of XPath is hard, while the other 90% is just useful) but it's always struck me as the perfect amount of structure and free-form.

The structure is only there to ensure that the content can be translated in to a variety of formats with a measure of sanity while the content tells you what the code does, like two people having a conversation. Not as a clinical abstraction but in the developer's own words.

Instead of clunky, artificial these are the inputs, these are the outputs. Tell a story about what happens when the function is used.

Michael Schwern, Documentation as Narrative

At this point the peanut gallery will tell you that they have to be so dedicated because there are so many ways to fuck Perl up. Personally, I'm okay with that. I will take actual code, with decent comments (read: documentation), that fucks something up, where I can turn the knobs and add debugging statements over a black box of contracts and interfaces — inputs and outputs — every single time. It is not only the best way to learn the mechanics of a language but also the idiosyncracies that govern it.

Example code is the view source of programming.

Anyway. That's just to say that I've never been predisposed to Java which has possibly the most thorough and thoroughly impossible documentation standards of any programming language today. Using JavaDoc feels like being forced to memorize a high school yearbook (or more likely a college facebook heavy in achievements but devoid of any useful information like whether a person snores in bed or is a mean drunk) rather than actually making friends with anyone.

Back during the job search before Flickr I actually set aside a couple hours each day to learn the language and set out to faithfully read the canonical primers and Understand the Principles. See, this is the classic mistake number one but since there is no example code outside of Sun's own HelloWorld-grade documentation it's easy to trick yourself in to believing it's a worthwhile use of your time. I was going to at least tackle class paths which seem to exist only as some sort of stubborn chamber of somber second thought to the language itself preventing you from doing anything until you've satisfied a laundry list of demands, grounded more in ritual than any apparent need.

Mostly what happened was that I started writing a book in my head titled Everything You Need To Know About Java You Already Know In Perl. It seemed to me that both languages did pretty much exactly the same thing in mostly the same ways. Or put another way : There's more than one way to write Perl, and one of them happens to be Java. Because the literature that surrounds it is so strident and earnest in tone, never mentions the gotchas or explains the fiercely reductionist view that it takes to every operation Java mostly just seemed like, well...a waste of time.

Untitled Insulation #1200879326

I mean, if you're going to suffer through terrible documentation (the exception to the rule, notwithstanding) then you might as well use Python since it's not nearly as mysterious in the theory and dogma that it levies against programmers.

Which is kind of sad because Java has a lot of really great libraries for, you know, doing stuff. At least that's what I kept telling myself. Then, all of a sudden, I was living in Vancouver back up to my neck in PHP wondering, like everyone else, just what the hell the order of arguments that you pass to any of the array functions is.

[pause]

I remember the first time I picked up a book of recipes by Elizabeth David. It was nothing but a collection of paragraphs. There were no ingredients lists or step by step instructions. Just the shapes of recipes, terrifying in their brevity. The point being that a pinch of salt is a little like the classic definition of obscenity : No one can tell what it is but everyone knows what it is when they see it.

This is not to say, despite the popular belief and current interest in aerosol beef... I mean molecular gastronomy, that computer programming is like cooking. Far from it. I mention the two together only to demonstrate that, while the two both tell you how to do something, the richness of story-telling the same recipe should serve as a counter-weight to the programmer's tendency to problem solve (or document) by boiling the sky.

Meanwhile — and I promise, this is the point — the Google/Android kids went and wrote an open source 2D barcode decoder library, called zxing. There are no shortage of encoders out there but the readers have always been mobile phone apps, libraries trapped in a twisty maze of C dependencies or ... written in Java, where the usefulness of the code (can anything else actually render SVG files?) never seemed to outweigh the headache of classpaths compounded by the lack of a simple run_me application compounded again by Java's worldview that reallyLongFunctionNames are better than even the tersest documentation.

I had more or less relegated zxing, written in Java of course, to the corner of my mind where projects go to never happen when I discovered that they'd also written a really simple web interface to decode barcodes.

So I finally learned Java (with the most excellent help of Tom and Dave and Paul who all suffered my questions, badgering and whining graciously).

If you do this :

you @ localhost in /home/yer/bin/ws-decode-0.1
$>./start.sh &
ws-decode server running on port : 9955
documentation and usage is available at http://localhost:9955/

you @ localhost in /home/yer/bin/ws-decode-0.1
$> curl http://127.0.0.1:9955

You get this :

ws-decode is a bare-bones HTTP interface for decoding 2D barcodes. It works like
this:

You send a (binary) POST request to http://localhost:9955/decode containing a
PNG or JPG file and the server will send back a plain-text version of its (the 
barcode in the image, of course) message body.

------------------------------------------------------
EXAMPLE
------------------------------------------------------

curl -v -H 'Content-Type: image/jpeg' -H 'Expect:' --data-binary \
  '@/Users/asc/Desktop/aa.jpg' http://127.0.0.1:9955/decode

  About to connect() to 127.0.0.1 port 9955
  * Trying 127.0.0.1...
  * connected
  * Connected to 127.0.0.1 (127.0.0.1) port 9955
  > POST /decode HTTP/1.1
  User-Agent: curl/7.13.1 (powerpc-apple-darwin8.0) libcurl/7.13.1
  OpenSSL/0.9.7l zlib/1.2.3
  Host: 127.0.0.1:9955
  Pragma: no-cache
  Accept: */*
  Content-Type: image/jpeg
  Content-Length: 4930

  < HTTP/1.1 200 OK
  < Date: Fri, 22 Feb 2008 06:50:39 GMT
  < Content-length: 28
  * Connection #0 to host 127.0.0.1 left intact
  * Closing connection #0

  http://aaronland.info/weblog

That's it. It will not make you a pony. No. No ponies for you.

Which does even less than the zxing service, but that's by design. The design, as such, is modeled closely after everything I learned building and using ws-modestmaps :

.

And now there's another little piece of the Papernet that has been built. Which is the important part.

No, I will not add a barcode for this link here : ws-decode 0.1

A short history of ws-modestmaps

This is the story I want to remember.

The first time I heard about the command line interface in ModestMaps was at the bar. Mike and I had lishly agreed to meet and talk while I tried to watch the hockey game.

A few months later I sat, drinking coffee, ripping out the near-complete Turing machine that had taken root in the command line application's options parser.

I love slippy maps as much as the next person but my name is Aaron and I have a paper fetis^H^H^H I mean, I hate the Internet. At least the web. And probably electricity. Or, put another way, I haven't really had any new ideas in the last year and am still worming my way through the boring details that grow like barnacles on the side of the Papernet.

As a completely tangential aside, how is it that NSCAD still spells FAIL when it comes to the web? I mean, honestly : How?

Anyway, eventually Mike and I went for beers again and I explained that I had written a small HTTP-based wrapper around the command line application so that I could call it easily from the code that generates the PocketMod books I'd been working on.

That was also the day that I uttered those painful words : The worst part is that Dave Winer was right all along. I do not know Dave personally, find his online persona mostly irritating and still think that OPML is easily the dumbest format ever created but credit should be given where it is due. He may not have been the first person to think that it would be a good idea to give everything tiny web interface but he's certainly done more than anyone else to champion it.

It didn't do much more than that, and even then didn't expose the command line version's complete interface. All it could was render a map centered on a latitude and longitude and optionally paste a 75x75 pixel PNG image of a pinwin, with a zoomed in version of the map pasted in to its canvas.

So, was born ws-compose.py

Soon afterward, I added ws-compose support to the mmPDF.php class in time to print a handy booklet of coffee shops and restaurants in Victoria, for FOSS4G. Showing it around the conference we all agreed that it still suffered the fundamental problem with these things : That web-based tiles + consumer-grade printers = shitty maps.

For a long time I've been threatening to build some sort of JavaScript-y pirate map generator. Specifically, overlay an SVG (or Canvas) canvas on top of a static map or even a proper slippy map with a limited set of drawing controls to trace only the streets and landmarks that you care about.

Yes, like the stripped down idealized directions maps that came out of Microsoft Labs a few years ago. The one whose address on the Interweb I can never find. Exactly like that.

There are still a few problems with this approach. The first is that there is no magic for including street names with their corresponding strokes in the SVG layer. Finding time to feel the Open Street Maps (OSM) luv is the most elegant solution to this problem, by writing a simplified interface to select only those features you want included in a map. Since each features knows what it is it's pretty easy to include that metadata — the street name — with the final map. Where pretty easy means no one has built that sort of stripped-down web interface for OSM but there you go; there shouldn't be anything preventing someone from doing it beyond time and quirks-mode.

The brute-force approach to the problem, suggested by Schuyler, would be to call the especially clever Google reverse geocoding/directions hack using any point on a selected road as the input. This is as brittle as it is brutish and prone to all kinds of errors, failures and unexpected results but it's still a nice example of treating APIs as being like the nubby bits on individual pieces of Lego.

The second problem is where you actually store the newly created pirate maps so that they can be used by tools like mmPDF or ModestMaps. The clever solution would be to use Jordan Sissel's filesystem driver for storing real files in del.icio.us but that the hooks that allowed the hack to work have since been disabled. My hunch is that the Right Way to deal with the problem is squirt the data in to FeatureServer and then add a ModestMaps provider which will fetch and render the data without an underlying tile layer. Quick, look at my waving hands and you won't notice all the parts I haven't considered. But I think that would work.

Meanwhile, Mike went and wrote a pure-Python implementation of Bill Atkinson's dithering algorithm. This was especially useful because if you dither a map rendered with standard road tiles, even at small sizes like 300 pixels, you end up with something surprisingly...legible. A bit ugly but that's largely made up for by street names you can read and the collosal amount of hoop-jumping needed to do anything else. Plus, dithered aerial tiles are hawt. They make old-skool cities like Paris and London with their not-so-invisible hand of god urban layouts shine and save newer, North American, cities from looking like the grid of plasticine pegs that they sadly are.

But I digress.

The Python bindings for ModestMaps have always let you render two kinds of maps : one centered on a point spiraling outwards to fill a fixed height and width, the other that zooms out until it can fit the extent of a bounding box inside a fixed height and width.

That set the stage for the long slog towards what began as ws-kitchensink.py and was finally released as ws-pinwin.py. I wanted a map where I could specify a bounding box and zoom level (as a rule, street level) and just say : Tile it, size be damned!

For lack of any better name, I called these poster maps

And pinwins. All kinds of pinwins. Pinwins with different sizes. Pinwins that don't overlap. Pinwins with automagic shadows. Pick two.

The order of events was roughly : Mike holding my hand to work out adding multiple markers (pinwins) to a map, followed by me trying to be smart about calculating the criteria for giant maps and then giving in and using the JDFI solution. The code to generate the actual markers was pretty simply but tedious and it's still not even clear to me what the distinction between a marker and mrk_data is. Something like the latter needs to know about the former but not the other way around but the truth is that it's all still a twisty maze of variables used to juggle offsets, and differing coordinate systems, made even yet more complicated by the part where I can't make heads or tails of the PIL documentation for paths so the markers are nothing more than a bunch of primitives glued together with bubblegum and spray-painted white.

But, really, who cares because now you can do :

        &marker=mileend,45.525825499457,-73.5989034175872,600,180 \
	&marker=roy,45.521375561025756,-73.57049345970154 \
        &marker=cherrier,45.51978191639917,-73.56947422027588
                    

And get back :

        HTTP/1.x 200 OK
        Server: BaseHTTP/0.3 Python/2.5
        Date: Sun, 13 Jan 2008 01:08:37 GMT
        Content-Type: image/png
        Content-Length: 1946576
        X-wscompose-Image-Height: 1024
        X-wscompose-Image-Width: 1024
        X-wscompose-Map-Zoom: 14.0
        X-wscompose-Marker-mileend: 336,211,157,-131,600,180
        X-wscompose-Marker-roy: 667,285,629,165,75,75
        X-wscompose-Marker-cherrier: 679,312,641,192,75,75
                    

I care a little but mostly because the anchors — or the cones — for the markers aren't anti-aliased so it's on the list to try to check whether the Cario graphics libraries are present and use those instead. I mention that because as attractive as it is to do the Right Thing and find the Beautiful Truth in math sorting out the shadows, and later re-positioning, for the pinwins reminded me that the wrong way was actually better.

    def mk_perspective_shadow (self) :

        #
        # I R DUHM ... why won't someone explain to me the math behind :
        #
        # http://www.cycloloco.com/shadowmaker/
        # http://www.css.taylor.edu/~btoll/s99/424/res/mtu/Notes/geometry/geo-tran.htm
        # inkscape's extensions/perspective.py
        #
        # so that I can do this :
        #
        # im.transform(size, PERSPECTIVE, data)
        #

        pass
                    

I say Math is hard a lot, and mostly mean it.

I spent a lot of time badgering my friend Prasun, who is a computer-vision wonk, to explain how the eight values you need to do a persective transformation are derived. I managed to dig up lots of almost-solutions but nothing to do proper Google Maps style shadows on the fly.

Real shadows and all that.

Prasun never did explain the Math Shapes to me but instead pointed out that since the vantage point of the light source (casting the shadow) is always the same it was probably easier to just do some sloppy math once and apply that single transformation uniformly to every marker. Which is true but made more complicated by having to spoof the perspective on the rounded corners. At least the fact that the shadows are semi-opaque and, typically, sitting on top of a background image means you can rely on the human eye to compensate (read: not notice) some of the quirks that remain.

Still. There is no math here. Only shapes ballparked relative to the size of the marker itself and duct tape.

The next step was to try and figure out how to deal with markers clustered near and overlapping one another. This is some sort of classic design/visualization problem, I guess, but I quickly decided that just exploding all the markers out from their anchor point would acheive nothing more than covering the surface of the map in pinwins.

The alternative — a rain-drop style layout like the one shown on the right — is not entirely satisfying because simply stacking nearby markers along the y-axis makes for ridiculously large images. Ridiculously large quickly becomes out-of-memory and bus errors when you render a large bounding box at street level (the center of Paris out to, say, Charles de Gaulle airport) but it also proves that true to life shadows for markers with lengthened markers only serve to distract from the final image.

This was also around the time when I realized that I would have to add an option to bleed an image, to adjust its final height and width to ensure that any markers wouldn't be cropped by the dimensions of the map tiles. I've been asked to add the option to fill in the bleed space with more map tiles (on the list) but I happen to like the white space adding a kind of anti-perspective to the whole thing. It's also pretty obvious, then, that the shadows are waging their private war on perspective shooting up in to space only to flop down, suddenly, when they reach the marker canvas.

On the other hand, since the markers have no outline of their own the misplaced shadow serves to outline just enough of the shape to tell you what's going on without too much distraction. I can't imagine realistic shadows spreading out across the bleed space like zebra stripes as anything but annoying. And ugly.

The pursuit of a faithful representation is laudable enough but, frankly, save it for your ray-tracer. We're talking about maps, here.

Sooner still I would like to add the hooks to pivot a marker canvas ninety degrees so that a very tall image could in fact be printed as a very long image. Long is useful because it means you could print and zebra-fold the whole thing in to a book. Or tile the final image again and print them out as individual pieces on ... say postcards and stitch them back in to something entirely new.

And yes, of course, re-tiling images back in to plain old map tiles. Oh yes...

For starters, though, there is only the obligatory Flickr hack. Though more or less functionally complete, near or before a final release I wanted to update Net::Flickr::Geo.pm to play nicely with ws-modestmaps and then some. Besides the grunt work of building custom markers (ws-pinwin does not handle compositing an image in to a marker's canvas) you can now ask it to fetch all the geotagged photos for a set, plot its bounding box and generate a poster map. For extra points you can also ask it to chop the final image in to 4 x 6 sized pieces and have the whole bloody thing re-uploaded to Flickr.

Some day, the nice printing services will have public-facing APIs.

All the pieces of ws-modestmaps are checked in to the ModestMaps source tree and there's even a real tutorial with working sample code. There's also support for rendering arbitrary polylines coming soon but it's time to let this one lie down and have a rest for a while.

I'm sure I've forgotten some tiny and important anecdote (more likely, just spelling mistakes) but I feel like I've been working on this post for as long as I have the code — hello cow orkers, I will come in to the office soon, I promise — so I'll just leave it at : Patches are welcome.

Oh, and, pinwins large enough to fit moo.com stickers need to be 126 pixels square. I'm just saying...