I’ve been chatting with some colleagues about a possible application. My purpose in working on it here, in this article, is to give a little insight into how it’s possible to “get started” when it seems that you really don’t know enough to do anything. We’ll see how successful I am.
Suppose that we had access to mapping information from the various map-serving companies: Microsoft, DeLorme, whoever. We know they’re out there; even if we don’t know all their names, we could probably find out pretty quickly. And suppose that we had access to the location of something or somebody interesting: our kids, our rental cars, our truck drivers. We know there are people interested in the locations of people and things they’re interested in. We could write a program that people could use to find out where their things are! This could be worth millions!
Now, like it or not, this is just about where every new product idea starts. Someone has some notion of putting together a couple of technical things, or has a vague notion that the world would be better if only. The possibilities are immediately endless. Everyone needs this product or service! It’s obvious! Probably right now, there’s something that you’d like to know where it is.
Now we could do a lot of analysis, look into mapping companies, assess their software, look at programming languages and environments, figure out what they have to offer, check into the market to see who has lost their truck recently, and so on. And maybe that’s a good thing for someone to do. But often, the entrepreneur wants to know something else: how hard would it be to write this program? Or he may want a functioning prototype of the program to demonstrate, to raise interest and assess the market.
We’re Extreme Programmers. Our mission is to find out the things we need to know to answer how hard the program is to write, and to provide a demonstration of what’s possible.
We always have assumptions. Here are some of ours, in more or less random order:
- Maps are available over the Internet. We know that Microsoft has a mapping service, and we've seen MapQuest. So the maps are out there.
- Maps are probably accessed by some kind of SOAP or similar Web Service protocol. On the one hand, we have no idea what the protocol is. On the other hand, we know a lot about it: it will probably require us to provide some kind of input on who we are, what area we want a map of, maybe some stuff about what level of detail, and the like. Do the details of the transaction matter? Well, they will when we build it, but they aren't likely to change the overall cost of the program much.
- The user will have a list of interesting things to put on his map. These might be cars or trucks or kids or kittens. Do we care? Well, when we build it. But the main flow of the program will be selectThing; displayThingOnMap. Details won't matter.
- There will be a map on the screen. This is kind of given, though I suppose the program could shout out "Your kid's at the mall!" Maybe later. There will be a map.
- The map will have a marker on it. A little kid-shaped icon, one with a skirt for your daughter, one with pants for the boy. Who knows. Maybe a circle would do.
- The map should probably center on the thing located.
- The map should be able to zoom in and out.
We could go on and on, no doubt, and some feature provider surely will. But I’m thinking we already have enough information to ask some questions and start getting some answers.
Here are some things I’d like to know right now:
- What's the shape of a program that accepts an input like "Find Fluffy" and displays a map with Fluffy's location on it? On the one hand, it might be no more difficult than a program where you select a text file name and it displays the text. On the other hand, the graphical display thing could be complicated.
- So how do you display a graphic anyway?
- What if the program has to center the map on Fluffy, and zoom in and out a bit? What's the shape of a program that does that? What complexity does it add?
I feel that if I knew that much, I’d know a lot about the essence of the program. I’d be left with unknowns about details, and some of those details could take time. But mostly I wouldn’t expect them to change the essence of the design. Am I right about that expectation? Well, let’s talk about it later.
Here are some things I’d like to know pretty soon:
- What requests can some typical map server accept? I'd like to know this mostly because it might give me ideas about things the program could do. I don't think it's likely to change how I write the program.
- How are map server requests formatted? Are they SOAP? Some other protocol? I'd like to know this because my experience is that connecting to remote information is always a hassle and I'd like to know more about this case. I'm already sure that the protocol will seem pretty easy, and that the reality will be that the details matter. But I don't think it'll change the shape of the program. It might change the cost of communicating with the map server.
- What kind of object does the map server return? My guess is that it will be an image, because the services work over the web and that's about all a web browser understands. But whatever it is, I know there's a way to render it to the screen, so the details won't change the shape of the program.
- Does the server handle panning and zooming? My guess is that it will, since otherwise MapQuest would be really slow while my browser sorted out the entire map of the USA. But if it doesn't, it won't change the shape of my program much, it will just add work that has to be done.
Does that last one seem a bit too brave? Well, we’ll see. But I know a little bit about how zooming and panning are done, and I figure there’s probably some support in my language, so while it might add work if we have to do it, I don’t think it’ll change the essentials.
Let's Start Learning
I’m going to do this experiment in VisualWorks Smalltalk, just because it’s this year’s tool of choice. Before deciding how to really write the program, I might implement it again in other languages. Maybe C#.
I don’t know how to do graphics in Smalltalk, but I know it’s in there. A little surfing and digging through the documents takes me to some examples. The examples all draw on a window, but not in a pane of the window. But still, they’re interesting, and in an hour, I have this sample code:
| gc ronImage | gc := (Examples.ExamplesBrowser prepareScratchWindow) graphicsContext. ronImage := (ImageReader fromFile: 'C:\Documents and Settings\Ron\My Documents\Data\XProgramming\site\images\ron.jpg') image. red := ronImage palette indexOf: ColorValue red.0 to: 10 do: [ :xy | ronImage atPoint: xy+40@xy+40 put: red. ronImage atPoint:50-xy@xy+40 put: red]. ronImage displayOn: gc. littleRon := ronImage shrunkenBy: 2@2. littleRon displayOn: gc at: 200@0. gc paint: ColorValue red. circle := Circle center: 0@0 radius: 10. gc translation: 20@20. circle displayFilledOn: gc. circle := circle scaledBy: 1/2. circle := circle translatedBy: 210@10. gc translation: 0@0. circle displayFilledOn: gc.
We’ll look at that in a little more detail in a minute, but first look at what that draws:
Two pictures of me, one larger than the other. A red X near my left eye. A red dot near my forehead. Now let’s review that code:
gc := (Examples.ExamplesBrowser prepareScratchWindow) graphicsContext. ronImage := (ImageReader fromFile: 'C:\Documents and Settings\Ron\My Documents\Data\XProgramming\site\images\ron.jpg') image. red := ronImage palette indexOf: ColorValue red. 0 to: 10 do: [ :xy | ronImage atPoint: xy+40@xy+40 put: red. ronImage atPoint:50-xy@xy+40 put: red]. ronImage displayOn: gc.
OK. That sets up a window, grabs its “graphicContext”. That’s a thing you can draw on. Then we read in an image from a convenient jpg file. The patch ends by displaying the image on the context. That, very likely, displays the big picture of me. What’s that stuff in the middle? We loop zero to ten. At the point (40+xy, 40+xy) we put red. That should draw a red diagonal line going right and down, right? (increasing y goes downward.) Then at (50-xy, 50+xy) we put red. That will start x at 50 and count back to 40, also increasing y (down). That’s a red diagonal line going left and down. That code draws the X.
What was interesting to me, though, was that it draws the X right into the picture: it marks the bits directly into the JPG image in memory. That’s why the X shows up in both pictures. But the dot … that shows up in both pictures too … but it’s done in a very different way. Here’s the rest of the code:
littleRon := ronImage shrunkenBy: 2@2. littleRon displayOn: gc at: 200@0. gc paint: ColorValue red. circle := Circle center: 0@0 radius: 10. gc translation: 20@20. circle displayFilledOn: gc. circle := circle scaledBy: 1/2. circle := circle translatedBy: 210@10. gc translation: 0@0. circle displayFilledOn: gc.
We create a new image, shrunken by 2 in each direction. Reading the Image>>shrunkenBy: method, I learn that you can shrink an image differently in each direction, and that you can only use integer shrink sizes. Can’t shrink by 1.5. That means the smallest zoom out we could get would be a factor of 2, if we use this feature in the obvious way. Not great news but let’s not panic.
We also learn that we can display an image anywhere in the graphics context, by using that #displayOn:at: method. Now, what about other ways of drawing? We create a circle object. That’s just an object representing a circle of radius 10, centered at (0,0) (that’s 0@0 in Smalltalk terms). But the circle isn’t displayed at 0@0, because we said
That means that whatever we tell the gc to do from now on, it will add 20@20 to the coordinates. So when we say
circle displayFilledOn: gc.
The circle goes to 20@20, on my forehead in the leftmost picture. Then we experiment a bit more. We make a smaller circle, scaled by 1/2. And we move that circle, from 0@0 where it started to 210@10. Since we put the littleRon picture at 200@0, that should put the little circle in the same place as in the big picture … except that the graphicsContext is also translating. If we leave that alone, the circle will show up at (200@10) + (20@20) or 220@30. That would be wrong. So we set the gc translation back to zero, and display the circle.
Why am I doing the same things in all these different ways? Because I’m trying to learn a little bit about what’s possible in display. Here’s some of what I’ve learned:
- It's possible to edit a picture directly, drawing right on it. It's difficult, because things like Circle don't know how to draw to an image. (They might know how to draw to another thing, called a Pixmap, that I read about along the way. That might be useful.) Anyway, putting the picture of Fluffy right into the map seems too tricky.
- It's possible to draw "on top" of a picture or map, by just drawing after. This means we could get the map, draw it to the screen, then put Fluffy's location icon in last.
- The screen can do simple translation (shifting in X and Y) for us. That might come in handy. Or, we can just tell Fluffy his coordinates relative to the corner of the screen, and he'll show up in the right place. We've tried it both ways.
- The screen cannot do scaling. That's why I had to make the Circle smaller explicitly: there was no scaleBy: method on the screen to go with the translateBy. So if we want to change the sizes of things, we'll have to do it somewhere other than in the screen itself.
So this experiment has told me quite a bit about how graphics work. I didn’t just type that whole thing in all at once, of course. I did it a little at a time, and I made plenty of mistakes along the way. Still, it only took me an hour. I tried lots of little tests: here’s another one and its result:
"darn! screen graphics contexts can't scale" "but they CAN translate and clip" | gc ronImage | gc := (Examples.ExamplesBrowser prepareScratchWindow) graphicsContext. ronImage := (ImageReader fromFile: 'C:\Documents and Settings\Ron\My Documents\Data\XProgramming\site\images\ron.jpg') image. red := ronImage palette indexOf: ColorValue red.0 to: 10 do: [ :xy | ronImage atPoint: xy+40@xy+40 put: red. ronImage atPoint:50-xy@xy+40 put: red]. ronImage displayOn: gc. gc clippingRectangle: (0@0 extent: 250@50). gc translateBy: 200@0. ronImage displayOn: gc. gc clippingRectangle: nil. gc translateBy: 50@50. ronImage displayOn: gc.
There’s some interesting learning in here. See that code about clippingRectangle? That’s saying that no matter how big the graphicsContext really is, further operations should only affect the area 250 wide and 50 high, starting from the top left corner. So when I write the image at 200@0, only the top left 50 pixels of Ron show up. I also tried this with the translateBy: first. That didn’t work as I expected: the whole x-width of the picture showed up. I believe that means that the clippingRectangle is adjusted by the translateBy: method on the way in. I’m not sure of the details, though I see that they interact oddly. We’ll be able to do whatever we need, so I”m not interested in digging into that any deeper right now.
What About Tests? What About Objects??
So. A couple of hours of fiddling around in workspaces, and I know a lot more than I used to about graphics in Smalltalk. That’s good. What might have been better?
It might be argued that I should have done all this with tests. Well, I don’t know how to do graphical tests very well, since I need to see with my eyes what happened. So I tend to start with little experiments.
But maybe I should have recorded those tests, at least, as a Smalltalk class. That might make good sense. Honestly, I’m not sure when I go for a class and when I just experiment in a workspace. For sure, though, if I don’t record these little patches of code somewhere, I’m likely to forget what I did, and have to relearn it. That’s definitely worth thinking about. There are a lot of options. I can save the workspace contents directly as files: that’s a feature of the Workspace object. Or I can save the code as files on my own. Or I can build up some classes. It’s even possible to do nothing, and leave the code lying around in Smalltalk’s changes files. I’ve done that in the past: worked on something that I didn’t save, and later on searched all the change files to find it. That often works – but not always. It’s possible to “compress changes”, which causes Smalltalk to throw away any code that isn’t currently in an existing class or method. We’ll have to think about how much of this to save, and how much will be likely to appear in real code that we’re about to write.
That’s all about documentation, and I’m going to digress to talk about that next. Stay tuned.
For now, we don’t have any tests, nor do we have any objects. But we do have some new comfort with graphics. We’ve seen how to display a JPG in Smalltalk, at least right on the surface of a window. There’s probably more to putting it inside a window pane, but if all else fails, the clippingRectangle: could help us with that. We’ve seen that we can scale things up and down, and that we can move their origin around.
Hey! That’s almost everything we need to do graphically in this application. There will be lots of details, but for me, I’ve changed my situation from one of fear and ignorance to a little less ignorance, and a lot less fear. Time for a break!