<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>And now it’s all this</title>
	<atom:link href="http://www.leancrew.com/all-this/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.leancrew.com/all-this</link>
	<description>I just said what I said and it was wrong. Or was taken wrong.</description>
	<lastBuildDate>Sat, 04 Feb 2012 03:23:05 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Address Book URLs</title>
		<link>http://www.leancrew.com/all-this/2012/02/address-book-urls/</link>
		<comments>http://www.leancrew.com/all-this/2012/02/address-book-urls/#comments</comments>
		<pubDate>Sat, 04 Feb 2012 03:23:05 +0000</pubDate>
		<dc:creator>Dr. Drang</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[address book]]></category>
		<category><![CDATA[applescript]]></category>
		<category><![CDATA[mac]]></category>
		<category><![CDATA[programming]]></category>

		<guid isPermaLink="false">http://www.leancrew.com/all-this/?p=1755</guid>
		<description><![CDATA[Last night, Brett Terpstra wrote a post about how to get links to Notational Velocity/nvALT notes into your Address Book via AppleScript. Since I don&#8217;t use either of the NVs, linking from Address Book to a note has no value for me, but I have needed to link to Address Book. Brett&#8217;s links work because&#8230;]]></description>
			<content:encoded><![CDATA[<p>Last night, <a href="http://brettterpstra.com/connecting-nvalt-and-address-book/">Brett Terpstra wrote a post</a> about how to get links to <a href="http://notational.net/">Notational Velocity</a>/<a href="http://brettterpstra.com/project/nvalt/">nvALT</a> notes into your Address Book via AppleScript. Since I don&#8217;t use either of the NVs, linking from Address Book to a note has no value for me, but I have needed to link <em>to</em> Address Book.</p>

<p>Brett&#8217;s links work because the NVs are registered to respond to the <code>nv://</code> URL scheme. Similarly, the Address Book is registered to respond to <code>addressbook://</code> URLs. A full URL to a particular contact in your Address Book will look something like this:</p>

<pre><code>addressbook://68A46B71-150A-4732-A183-D99EECCE1F18:ABPerson
</code></pre>

<p>That happens to be the URL to the Apple, Inc. entry in my Address Book.</p>

<p>I&#8217;ve written about this a <a href="http://www.leancrew.com/all-this/2007/02/address-book-scripts-part-2/">couple</a> of <a href="http://www.leancrew.com/all-this/2008/06/address-book-urls-revisited/">times</a>, and have incorporated links to contacts in <a href="https://github.com/drdrang/notes">PNotes</a>, the no-server personal wiki I use to keep track of project notes at work.</p>

<p>The portion of the Address Book URL after the double slashes can&#8217;t, as far as I know, be extracted directly from Address Book itself, but it is accessible via AppleScript. Here&#8217;s a simple script to get the URL for a selected contact and put it on the clipboard.</p>

<pre><code>1:  tell application "Address Book"
2:    set ABURLs to ""
3:    set contacts to the selection
4:    repeat with thisPerson in contacts
5:      set ABURLs to ABURLs &amp; ("addressbook://" &amp; id of thisPerson) &amp; return
6:    end repeat
7:  end tell
8:  set the clipboard to characters 1 thru -2 of ABURLs as text
</code></pre>

<p>If you have several contacts selected, it will return URLs to all of them, one per line.</p>

<p>The key to the script is Line 5, where the URL is constructed from the <code>id</code> property of the current contact. When the <code>repeat</code> loop is finished, there&#8217;s a trailing newline at the end of the <code>ABURLs</code> string. This is stripped just before the string is put on the clipboard in Line 8.</p>

<p>If you think there&#8217;s something funny about this line, you&#8217;re not the only one. As you can see, AppleScript, like many languages, can count back from the end of a string by using a negative number. The <code>-2</code> in Line 8 refers to the &#8220;second to last&#8221; character in the string; <code>-1</code> would refer to the last character. What I find odd about this is that other languages that use negative string indices—Perl, Python, and Ruby, for example—treat the index as an <em>offset</em>, with positive numbers (and zero) as offsets from the beginning of the string and negative numbers as offsets from the end. AppleScript, though, doesn&#8217;t use offsets in the forward direction; the first character in a string is <code>character 1</code>, not <code>character 0</code>. Why does it use offsets in the backward direction?</p>

<p>(Am I overthinking this? No doubt. I&#8217;m sure AppleScript&#8217;s designers would argue that you count characters the same way forward and backward but use negatives when counting backward. Still, if you&#8217;re used to the way character offsets work in other languages, AppleScript&#8217;s indexing seems inconsistent.)</p>

<p>Another thing that may strike you as weird is the <code>as text</code> at the end of Line 8. Isn&#8217;t <code>ABURLs</code> already text? Yes, it is, but</p>

<pre><code>characters 1 thru -2 of ABURLs
</code></pre>

<p>is a list. The <code>as text</code> is necessary to turn that list back into text.</p>

<p>So, once you have an Address Book URL, what can you do with it?<sup id="fnref:q"><a href="#fn:q" rel="footnote">1</a></sup> In local web pages, like PNotes, you can link someone&#8217;s name to the corresponding Address Book entry:</p>

<pre><code>&lt;a href="addressbook://68A46B71-150A-4732-A183-D99EECCE1F18:ABPerson"&gt;Apple&lt;/a&gt;
</code></pre>

<p>In scripts, you can use Apple&#8217;s <a href="http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/open.1.html"><code>open</code> command</a> to do the same thing:</p>

<pre><code>open addressbook://68A46B71-150A-4732-A183-D99EECCE1F18:ABPerson
</code></pre>

<p>Unfortunately, <a href="http://itunes.apple.com/us/app/omnioutliner/id404478020?mt=12&amp;partnerId=30&amp;siteID=L4JhWyGwYTM">OmniOutliner</a>, an otherwise lovely program, doesn&#8217;t recognize Address Book URLs, so unlike <code>http://</code> links, strings that start with <code>addressbook://</code> don&#8217;t get automatically turned into clickable links. That&#8217;s something the OmniGroup could improve on.</p>

<p>Another unfortunate thing about Address Book URLs is that they don&#8217;t seem to be consistent across computers, even with iCloud syncing. The link I gave above for the Apple entry in my Address Book works only on my MacBook Air. On my iMac, that same entry has this link:</p>

<pre><code>addressbook://313FB5BC-F075-48F2-8BE1-BBC1F31FD374:ABPerson
</code></pre>

<p>I assume the difference has something to do with the algorithm Address Book uses to generate an entry&#8217;s <code>id</code>. Maybe the <code>id</code> isn&#8217;t part of the package of data that gets synced, but is created on the fly as the entry is imported. Whatever the reason, it would be nice if the links were consistent across my computers.</p>

<div class="footnotes">
<hr />
<ol>

<li id="fn:q">
<p>Does it seem like this post has an awful lot of questions? Are you tired of me using that rhetorical device? Me, too.&#160;<a href="#fnref:q" rev="footnote">&#8617;</a></p>
</li>

</ol>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.leancrew.com/all-this/2012/02/address-book-urls/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Afghanistan, January 2012</title>
		<link>http://www.leancrew.com/all-this/2012/02/afghanistan-january-2012/</link>
		<comments>http://www.leancrew.com/all-this/2012/02/afghanistan-january-2012/#comments</comments>
		<pubDate>Sat, 04 Feb 2012 01:16:45 +0000</pubDate>
		<dc:creator>Dr. Drang</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[afghanistan]]></category>
		<category><![CDATA[politics]]></category>
		<category><![CDATA[war]]></category>

		<guid isPermaLink="false">http://www.leancrew.com/all-this/?p=1754</guid>
		<description><![CDATA[With the war in Iraq officially over, from now on I&#8217;ll be graphing US and coalition military deaths in Afghanistan only. The source of my figures remains icasualties.org, which continues to present the Iraq casualty history as well as the ongoing casualties in Afghanistan. Before we let it go, though, I think it&#8217;s worth reflecting&#8230;]]></description>
			<content:encoded><![CDATA[<p>With the war in Iraq officially over, from now on I&#8217;ll be graphing US and coalition military deaths in Afghanistan only. The source of my figures remains <a href="http://icasualties.org">icasualties.org</a>, which continues to present the Iraq casualty history as well as the ongoing casualties in Afghanistan.</p>

<p>Before we let it go, though, I think it&#8217;s worth reflecting on these totals for Iraq:</p>

<ul>
<li>US military deaths: 4484</li>
<li>Coalition military deaths: 4802</li>
<li>Weapons of mass destruction: 0</li>
<li>Connections with September 11 terrorists: 0</li>
<li>Threats posed to US and her allies: 0</li>
</ul>

<p>As for Afghanistan, a war that was easy to justify in 2001 but not today, January had an uptick in military deaths.</p>

<p><a href="http://www.leancrew.com/all-this/images2011/ac-2012-01.png"><img class="ss" src="http://www.leancrew.com/all-this/images2011/ac-2012-01-t.png" alt="Afghanistan January 2012" title="Afghanistan January 2012" /></a></p>

<p>With luck, we won&#8217;t see the big increases that have occurred every spring for the last three years.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.leancrew.com/all-this/2012/02/afghanistan-january-2012/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Über</title>
		<link>http://www.leancrew.com/all-this/2012/02/uber/</link>
		<comments>http://www.leancrew.com/all-this/2012/02/uber/#comments</comments>
		<pubDate>Fri, 03 Feb 2012 04:17:03 +0000</pubDate>
		<dc:creator>Dr. Drang</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[productivity]]></category>
		<category><![CDATA[programming]]></category>

		<guid isPermaLink="false">http://www.leancrew.com/all-this/?p=1753</guid>
		<description><![CDATA[This was a little disconcerting to see in my Twitter stream: Nice. @TextExpander übergeek @drdrang has turned his attention to @PDFpen + AppleScript. Productivity nerds rejoice! smle.us/wFBGv9&#160;&#160;&#8212; Jean MacDonald (@macgenie) Thu Feb 2 2012 Übergeek? Really? I mean, I really like that script because it uses PDFpen to turn a tedious, 2-3 minute task into&#8230;]]></description>
			<content:encoded><![CDATA[<p>This was a little disconcerting to see in my Twitter stream:</p>

<div class="bbpBox" id="t165168250789953536"><blockquote><span class="twContent">Nice. <a href="http://twitter.com/TextExpander">@TextExpander</a> übergeek <a href="http://twitter.com/drdrang">@drdrang</a> has turned his attention to <a href="http://twitter.com/PDFpen">@PDFpen</a> + AppleScript. Productivity nerds rejoice! <a href="http://smle.us/wFBGv9">smle.us/wFBGv9</a></span><span class="twMeta"><br /><span class="twDecoration">&nbsp;&nbsp;&mdash; </span><span class="twRealName">Jean MacDonald</span><span class="twDecoration"> (</span><a href="http://twitter.com/macgenie"><span class="twScreenName">@macgenie</span></a><span class="twDecoration">) </span><a href="https://twitter.com/#!/macgenie/status/165168250789953536"><span class="twTimeStamp">Thu Feb 2 2012</span></a><span class="twDecoration"></span></span></blockquote></div>

<p>Übergeek? Really? I mean, I really like <a href="http://www.leancrew.com/all-this/2012/02/automatic-w-9s-with-pdfpen/">that script</a> because it uses PDFpen to turn a tedious, 2-3 minute task into a one-step, 2-3 second task, but there isn&#8217;t much to it. The trickiest part was getting the syntax right for adding text to a PDF. How did I work that out? As I said, I stole it from this script,</p>

<pre><code> 1:  -- This script allows the user to enter text to imprint on all pages.
 2:  
 3:  tell application "PDFpenPro"
 4:    if (count documents) &gt; 0 then
 5:      set dialogResult to display dialog "Enter text:" default answer "Draft"
 6:      if button returned of dialogResult is not null then
 7:        set theText to text returned of dialogResult
 8:        tell document 1
 9:          set imprintCount to 0
10:          set pageCount to count pages
11:          repeat with aPage in pages
12:            set theImprint to make new text imprint with properties {rich text:theText} at end of imprints of aPage
13:            set font of attribute run 1 of rich text of theImprint to "LucidaGrande-Bold"
14:            set size of attribute run 1 of rich text of theImprint to 30
15:            set color of theImprint to {1, 0, 0}
16:          end repeat
17:          return pageCount
18:        end tell
19:      end if
20:    end if
21:  end tell
</code></pre>

<p>which should look familiar to Jean. It&#8217;s called &#8220;Imprint All Pages With Text,&#8221; and it&#8217;s provided by Smile Software as an example of the kinds of thing you can do with <a href="http://itunes.apple.com/us/app/pdfpen-for-mac/id403624960?mt=12&amp;partnerId=30&amp;siteID=L4JhWyGwYTM">PDFpen</a> and <a href="http://itunes.apple.com/us/app/pdfpenpro-for-mac/id403758325?mt=12&amp;partnerId=30&amp;siteID=L4JhWyGwYTM">PDFpenPro</a>. See any resemblance between Lines 12-15 of that script and Lines 11-14 of mine?</p>

<pre><code>11:    set stamp to make new text imprint with properties {rich text:dstamp, x position:410, y position:262, width:144, height:16} at end of imprints of page 1 of document 1
12:    set font of attribute run 1 of rich text of stamp to "Helvetica"
13:    set size of attribute run 1 of rich text of stamp to 12
14:    set color of attribute run 1 of rich text of stamp to {0, 0, 0}
</code></pre>

<p>I would never have been able to write my script without the example provided by Smile. I knew from PDFpenPro&#8217;s AppleScript dictionary that it had something called <code>text imprints</code> and that they had a <code>rich text</code> property, but I had no idea how to set and style the <code>rich text</code> until I saw the sample code.</p>

<p>By the way, this illustrates an important point. There are only two ways to write AppleScript code:</p>

<ol>
<li>Copy someone else&#8217;s and adapt it.</li>
<li>Keep trying different syntax until something works.  </li>
</ol>

<p>Most of the time I have to use both approaches. Is it any wonder I hate AppleScript? I&#8217;ve been programming in it since the mid-90s and I still feel like a novice.</p>

<p>So anyway, there was nothing übergeeky about that script. No clever algorithms, no brilliant insight. I just had an idea for saving time, and I hammered away at the script until it worked.</p>

<p>Which isn&#8217;t to say I can&#8217;t be übergeeky in my real area of expertise. I&#8217;ve <a href="http://www.leancrew.com/all-this/2011/02/oil-can-what/">written</a> <a href="http://www.leancrew.com/all-this/2011/09/lets-twist-again/">several</a> <a href="http://www.leancrew.com/all-this/2011/01/coke-cans-pull-tabs-and-the-class-struggle/">posts</a> <a href="http://www.leancrew.com/all-this/2011/02/daniel-bernoulli-is-in-your-toilet/">like</a> <a href="http://www.leancrew.com/all-this/2010/07/bathroom-scales-and-robert-hooke/">that</a>. And when I finally get around to writing my long-delayed post on 19th-century suspension bridges, fluxions, and the tenacity of iron—hinted at in <a href="http://www.macdrifter.com/2012/02/dr-drangs-writer-workflow/">this interview over at MacDrifter</a>—the geekiness will be über indeed.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.leancrew.com/all-this/2012/02/uber/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Automatic W-9s with PDFpen</title>
		<link>http://www.leancrew.com/all-this/2012/02/automatic-w-9s-with-pdfpen/</link>
		<comments>http://www.leancrew.com/all-this/2012/02/automatic-w-9s-with-pdfpen/#comments</comments>
		<pubDate>Thu, 02 Feb 2012 04:35:16 +0000</pubDate>
		<dc:creator>Dr. Drang</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[applescript]]></category>
		<category><![CDATA[pdfpen]]></category>
		<category><![CDATA[productivity]]></category>
		<category><![CDATA[programming]]></category>

		<guid isPermaLink="false">http://www.leancrew.com/all-this/?p=1752</guid>
		<description><![CDATA[I resisted PDFpen for a long time. I already had OmniGraffle, and while it certainly wasn&#8217;t convenient to fill out PDF forms in OmniGraffle, I could do it. This worked fine as long as filling out PDF forms was a rare task. But as the companies I work with began using PDFs more and more,&#8230;]]></description>
			<content:encoded><![CDATA[<p>I resisted <a href="http://itunes.apple.com/us/app/pdfpenpro-for-mac/id403758325?mt=12&amp;partnerId=30&amp;siteID=L4JhWyGwYTM">PDFpen</a> for a long time. I already had <a href="http://itunes.apple.com/us/app/omnigraffle/id404458553?mt=12&amp;partnerId=30&amp;siteID=L4JhWyGwYTM">OmniGraffle</a>, and while it certainly wasn&#8217;t convenient to fill out PDF forms in OmniGraffle, I <em>could</em> do it. This worked fine as long as filling out PDF forms was a rare task. But as the companies I work with began using PDFs more and more, the inefficiencies of using OmniGraffle began to grate on me.<sup id="fnref:og"><a href="#fn:og" rel="footnote">1</a></sup> I bought <a href="http://itunes.apple.com/us/app/pdfpenpro-for-mac/id403758325?mt=12&amp;partnerId=30&amp;siteID=L4JhWyGwYTM">PDFpenPro</a> during a sale last month, and it&#8217;s already saved me a few of hours of fuss. And with the AppleScript I wrote today, I expect it to save me many more.</p>

<p>The AppleScript automates the chore of filling out <a href="http://www.irs.gov/formspubs/index.html?portlet=103">W-9 forms</a>. This is the IRS form in which you certify that you&#8217;re not subject to backup withholding. Our clients don&#8217;t want to deduct backup withholding from their payments to us (and we certainly don&#8217;t want that, either), and having this form on file justifies their not doing so. I&#8217;m convinced it&#8217;s a waste of time and effort, but if it makes them happy I&#8217;ll fill out the damned form.</p>

<p>Actually, from today forward, <em>I</em> won&#8217;t be filling out the W-9s—AppleScript and PDFpenPro will. All I&#8217;ll be doing is running the AppleScript and emailing the filled-out form to the client. Here&#8217;s how I&#8217;ve set things up.</p>

<p>First, I downloaded <a href="http://www.irs.gov/pub/irs-pdf/fw9.pdf">a PDF of the form</a> from the IRS and opened it in PDFpenPro.<sup id="fnref:pro"><a href="#fn:pro" rel="footnote">2</a></sup> It&#8217;s a four-page document, but three of the pages are instruction, so I deleted them from the document. Most of the form fields that need to be filled in—the company name, address, and EIN—never change, so I did that, leaving me with a document that looks like this:</p>

<p><a href="http://www.flickr.com/photos/drdrang/6804732589/"><img class="ss" src="http://farm8.staticflickr.com/7035/6804732589_08df344cd1_z.jpg" alt="W-9 template with the unchanging fields filled in" title="W-9 template with the unchanging fields filled in" /></a></p>

<p>This will be my base document, a template for all the customized W-9s I&#8217;ll be producing. I keep the template in my Dropbox folder so I can access it from either of my computers.</p>

<p>The only field that needs to be customized is the date next to my signature.<sup id="fnref:minimum"><a href="#fn:minimum" rel="footnote">3</a></sup> That&#8217;s filled out by this AppleScript:</p>

<pre><code> 1:  set today to current date
 2:  set dstamp to (month of today as text) &amp; " " &amp; (day of today as text) &amp; ", " &amp; (year of today as text)
 3:  
 4:  tell application "Finder"
 5:    set w9 to document file "Test W9.pdf" of folder "Dropbox" of home as text
 6:    set neww9 to (desktop as text) &amp; ":Test W9.pdf"
 7:  end tell
 8:  
 9:  tell application "PDFpenPro"
10:    open w9
11:    set stamp to make new text imprint with properties {rich text:dstamp, x position:410, y position:262, width:144, height:16} at end of imprints of page 1 of document 1
12:    set font of attribute run 1 of rich text of stamp to "Helvetica"
13:    set size of attribute run 1 of rich text of stamp to 12
14:    set color of attribute run 1 of rich text of stamp to {0, 0, 0}
15:    close document 1 saving in neww9
16:    quit
17:  end tell
</code></pre>

<p>Lines 1 and 2 get the date in the format I want to insert into the form. Lines 4-7 get the paths to the template form (in Dropbox) and the currently non-existent filled-out form (which will be on my Desktop). If you want to use this script, you&#8217;ll probably want to change the file name, if not the folders, of these paths.</p>

<div class="update">

<p><strong>Update 2/2/12</strong><br />
Here&#8217;s a funny thing. On my MacBook Air, the colon before the file name in Line 6 is essential to separate the folder name from the file name. On my iMac, having that colon there causes the file to be saved in my home folder rather than on the Desktop. Apparently <code>desktop as text</code> returns a path without a trailing colon on the MacBook Air and with a trailing colon on the iMac. I have no idea why there&#8217;s a difference (they&#8217;re both running the same version of Lion), but if you try the script out and it doesn&#8217;t save the result where you want, this path discrepancy might be the explanation.</p>

</div>

<p>Line 10 then opens the template in PDFpenPro and the fun begins. Much of what follows was taken almost directly from one of the sample AppleScripts that comes with PDFpen.</p>

<p>Line 11 adds the date as a &#8220;text imprint&#8221;—that&#8217;s a PDFpen-specific term for text you add to a PDF—to the template at the proper coordinates. Lines 12-14 then set the formatting for the date to black 12-point Helvetica. Any of the values in these lines can be tweaked to suit your needs.</p>

<p>Lines 15 and 16 save the dated form to a new file on my Desktop and quit PDFpenPro.</p>

<p>I now have a one-step method for generating a W-9 to send to my client. No hunting for the template, no typing, no worries about overwriting the template. I just call the AppleScript via <a href="http://itunes.apple.com/us/app/fastscripts/id446994638?mt=12&amp;partnerId=30&amp;siteID=L4JhWyGwYTM">FastScripts</a> and the filled-in form appears on my Desktop.</p>

<p><a href="http://www.flickr.com/photos/drdrang/6804743691/"><img class="ss" src="http://farm8.staticflickr.com/7021/6804743691_b1de961aa0_z.jpg" alt="W-9 filled in via AppleScript" title="W-9 filled in via AppleScript" /></a></p>

<p>FastScripts isn&#8217;t necessary, of course. It&#8217;s just what I&#8217;m used to. You can use any one of the seemingly endless methods for calling a script.</p>

<p>An obvious extension to this script would be to add a section that creates a new mail message with some &#8220;Here is your W-9&#8221; boilerplate and the filled-in form attached. An exercise for a later date. There&#8217;s only so much AppleScript I can take in one day.</p>

<div class="footnotes">
<hr />
<ol>

<li id="fn:og">
<p>This is not OmniGraffle&#8217;s fault. It&#8217;s not meant to be a PDF manipulator, but it can be one in a pinch. I just got tired of being pinched.&#160;<a href="#fnref:og" rev="footnote">&#8617;</a></p>
</li>

<li id="fn:pro">
<p>The Pro version is what I have, but as far as I know, everything I describe here can also be done with the <a href="http://itunes.apple.com/us/app/pdfpen-for-mac/id403624960?mt=12&amp;partnerId=30&amp;siteID=L4JhWyGwYTM">basic version</a>.&#160;<a href="#fnref:pro" rev="footnote">&#8617;</a></p>
</li>

<li id="fn:minimum">
<p>I could fill out the requester&#8217;s name and address, too, but that&#8217;s not required, and my goal here is to do the bare minimum.&#160;<a href="#fnref:minimum" rev="footnote">&#8617;</a></p>
</li>

</ol>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.leancrew.com/all-this/2012/02/automatic-w-9s-with-pdfpen/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Setting my coordinate</title>
		<link>http://www.leancrew.com/all-this/2012/01/setting-my-coordinate/</link>
		<comments>http://www.leancrew.com/all-this/2012/01/setting-my-coordinate/#comments</comments>
		<pubDate>Tue, 31 Jan 2012 04:15:25 +0000</pubDate>
		<dc:creator>Dr. Drang</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[exif]]></category>
		<category><![CDATA[photos]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[python]]></category>

		<guid isPermaLink="false">http://www.leancrew.com/all-this/?p=1751</guid>
		<description><![CDATA[Too many posts of my retuning of old scripts. This is another one, but I&#8217;ll try to make it the last one for a while. Today I was going to use my coordinate script to set the location of several dozen photos I took with a standard, non-GPS-equipped camera. I&#8217;d taken a photo in the&#8230;]]></description>
			<content:encoded><![CDATA[<p>Too many posts of my retuning of old scripts. This is another one, but I&#8217;ll try to make it the last one for a while.</p>

<p>Today I was going to use <a href="http://www.leancrew.com/all-this/2011/10/location-location-location/">my <code>coordinate</code> script</a> to set the location of several dozen photos I took with a standard, non-GPS-equipped camera. I&#8217;d taken a photo in the same location with my iPhone, and I intended to use the command</p>

<pre><code>coordinate -g iphone.jpg IMG*
</code></pre>

<p>to take the GPS location embedded in the iPhone photo and put it in the other photos. In fact, the only reason I took the iPhone photo was to use it to extract the GPS info. Well, this scheme went agley. Apparently, I took the iPhone photo before its GPS had figured out where it was, so the coordinates in it were useless.</p>

<p>I shifted to my backup plan: get the location from Google Maps and use the less automated form of <code>coordinate</code>, where I include the latitude and longitude explicitly on the command line. Unfortunately, I had written <code>coordinate</code> to expect the input in <code>dd:mm:ss.ss</code> format, not the decimal degrees format that Google gives you when you use its &#8220;Drop LatLng Marker&#8221; feature.<sup id="fnref:latlng"><a href="#fn:latlng" rel="footnote">1</a></sup></p>

<p><a href="http://www.flickr.com/photos/drdrang/6793514845/"><img class="ss" src="http://farm8.staticflickr.com/7156/6793514845_aa010503ba_z.jpg" alt="LatLng marker" title="LatLng marker" /></a></p>

<p>It&#8217;s not the hardest thing in the world to convert from decimal degrees to degrees, minutes, and seconds, but it seemed like something <code>coordinate</code> should do for me.</p>

<p>Now it does. Here&#8217;s the new code:</p>

<pre><code>python:
  1:  #!/usr/bin/env python
  2:  
  3:  import pyexiv2
  4:  import getopt
  5:  import sys
  6:  from fractions import Fraction
  7:  from functools import partial
  8:  from math import modf
  9:  
 10:  usage = """Usage: coordinate [options] [files]
 11:  
 12:  Options:
 13:    -g filename    photo file with GPS coordinates
 14:    -n angle       north coordinate
 15:    -s angle       south coordinate
 16:    -e angle       east coordinate
 17:    -w angle       west coordinate
 18:    -h             show this help message
 19:  
 20:  Add location metadata to each of the listed files. The location
 21:  can come from either the photo associated with the -g option or
 22:  with a -n, -s, -e, -w pair given on the command line. The angles
 23:  can be in either d.dddddd or dd:mm:ss.sss format."""
 24:  
 25:  # Functions for manipulating coordinates.
 26:  def makecoord(coordstring):
 27:    """Make a coordinate list from a coordinate string.
 28:    
 29:    The string can be either "d.ddddd" or "dd:mm:ss.ss", and the
 30:    list comprises three Fractions."""
 31:    
 32:    if ':' not in coordstring:
 33:      theta = float(coordstring)
 34:      m, d = modf(theta)
 35:      s, m = modf(m*60)
 36:      s = s*60
 37:      angle = [ str(x) for x in [d, m, s] ]
 38:    else:
 39:      angle = coordstring.split(':', 2)
 40:    
 41:    loc = [ Fraction(x).limit_denominator(1000) for x in angle ]
 42:    return loc
 43:    
 44:  def setcoord(metadata, direction, coordinate):
 45:    """Set the latitude or longitude coordinate.
 46:    
 47:    Latitude is set if direction is 'N' or 'S', longitude if 'E' or 'W'.
 48:    The coordinate is a list of the form [dd, mm, ss], where the degrees,
 49:    minutes, and seconds are Fractions."""
 50:    
 51:    tags = {'lat': ('Exif.GPSInfo.GPSLatitudeRef', 'Exif.GPSInfo.GPSLatitude'),
 52:            'lon': ('Exif.GPSInfo.GPSLongitudeRef', 'Exif.GPSInfo.GPSLongitude')}
 53:    if direction in ('N', 'S'):
 54:      coord = 'lat'
 55:    else:
 56:      coord = 'lon'
 57:    metadata[tags[coord][0]] = direction
 58:    metadata[tags[coord][1]] = coordinate
 59:  
 60:  
 61:  # Get the command line options.
 62:  try:
 63:    options, filenames = getopt.getopt(sys.argv[1:], 'g:n:s:e:w:h')
 64:  except getopt.GetoptError, err:
 65:    print str(err)
 66:    sys.exit(2)
 67:  
 68:  # Set the option values.
 69:  gpsphoto = north = south = east = west = False       # defaults
 70:  for o, a in options:
 71:    if o == '-g':
 72:      gpsphoto = a
 73:    elif o == '-n':
 74:      north = makecoord(a)
 75:    elif o == '-s':
 76:      south = makecoord(a)
 77:    elif o == '-e':
 78:      east = makecoord(a)
 79:    elif o == '-w':
 80:      west = makecoord(a)
 81:    else:
 82:      print usage
 83:      sys.exit()
 84:  
 85:  
 86:  # Valid option combinations.
 87:  ne = (north and east) and not (south or west or gpsphoto)
 88:  nw = (north and west) and not (south or east or gpsphoto)
 89:  se = (south and east) and not (north or west or gpsphoto)
 90:  sw = (south and west) and not (north or east or gpsphoto)
 91:  gps = gpsphoto and not (north or south or east or west)
 92:  
 93:  if not (ne or nw or se or sw or gps):
 94:    print "invalid location"
 95:    sys.exit()
 96:  
 97:  
 98:  # Create the coordinate setter functions.
 99:  if ne:
100:    setlat = partial(setcoord, direction='N', coordinate=north)
101:    setlon = partial(setcoord, direction='E', coordinate=east)
102:  elif nw:
103:    setlat = partial(setcoord, direction='N', coordinate=north)
104:    setlon = partial(setcoord, direction='W', coordinate=west)
105:  elif se:
106:    setlat = partial(setcoord, direction='S', coordinate=south)
107:    setlon = partial(setcoord, direction='E', coordinate=east)
108:  elif sw:
109:    setlat = partial(setcoord, direction='S', coordinate=south)
110:    setlon = partial(setcoord, direction='W', coordinate=west)
111:  elif gps:
112:    basemd = pyexiv2.ImageMetadata(gpsphoto)
113:    basemd.read()
114:    latref = basemd['Exif.GPSInfo.GPSLatitudeRef']
115:    lat = basemd['Exif.GPSInfo.GPSLatitude']
116:    lonref = basemd['Exif.GPSInfo.GPSLongitudeRef']
117:    lon = basemd['Exif.GPSInfo.GPSLongitude']
118:    setlat = partial(setcoord, direction=latref.value, coordinate=lat.value)
119:    setlon = partial(setcoord, direction=lonref.value, coordinate=lon.value)
120:  else:
121:    print "coordinate setter failed"
122:    sys.exit()
123:  
124:  # Cycle through the files.
125:  for f in filenames:
126:    md = pyexiv2.ImageMetadata(f)
127:    md.read()
128:    setlat(md)
129:    setlon(md)
130:    md.write()
</code></pre>

<p>The new parts are in the <code>makecoord</code> function, Lines 26-42. If the coordinate given on the command line doesn&#8217;t include a colon, it&#8217;s assumed to be in decimal degrees, and Lines 33-37 turn it into a three-item list of strings appropriate for the conversion to Fractions that occurs on Line 41. If the coordinate <em>does</em> include a colon, it&#8217;s treated as before on Line 39.</p>

<p>I used a new (to me) function: <code>modf</code> from <a href="http://docs.python.org/library/math.html">the <code>math</code> library</a>. It takes a floating point number as its argument and returns the fractional and integer parts as a tuple. Strangely, the fractional part comes <em>before</em> the integer part in the tuple. I assumed it was the other way around when I first wrote Lines 34-35, which led to some surprising results.</p>

<p>Now I can copy the coordinates from a LatLng marker and, with just a little editing on the command line, use them directly in the <code>coordinate</code> command:</p>

<pre><code>coordinate -n 41.87658 -w 87.61912 *.jpg
</code></pre>

<p>I should have written the script this way in the first place.</p>

<div class="footnotes">
<hr />
<ol>

<li id="fn:latlng">
<p>You know about that feature, right? You put the mouse pointer where you want the coordinates and right-click. A popup of options appears, one of which is &#8220;Drop LatLng Marker.&#8221; The result is the little flag you see in the screenshot.</p>

<p><strong>Update</strong> As Sven says in the comments, this isn&#8217;t a standard feature. You need to enable it in the Maps Labs settings (accessed through the gear icon in the upper right corner of the screen when you&#8217;re visiting Google Maps).&#160;<a href="#fnref:latlng" rev="footnote">&#8617;</a></p>
</li>

</ol>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.leancrew.com/all-this/2012/01/setting-my-coordinate/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Blackbird.py and background images</title>
		<link>http://www.leancrew.com/all-this/2012/01/blackbird-py-and-background-images/</link>
		<comments>http://www.leancrew.com/all-this/2012/01/blackbird-py-and-background-images/#comments</comments>
		<pubDate>Mon, 30 Jan 2012 05:42:30 +0000</pubDate>
		<dc:creator>Dr. Drang</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[twitter]]></category>

		<guid isPermaLink="false">http://www.leancrew.com/all-this/?p=1749</guid>
		<description><![CDATA[In his comment on last night&#8217;s post about that fraction with the interesting decimal representation, Matthew McVickar pointed out a bug in my system for displaying tweets: I’m curious about the background in that embedded tweet of mine. My Twitter profile background is solid blue, and I don’t have a background image set. Looking up&#8230;]]></description>
			<content:encoded><![CDATA[<p>In his comment on last night&#8217;s post about <a href="http://www.leancrew.com/all-this/2012/01/fun-with-pythons-decimal-library/">that fraction with the interesting decimal representation</a>, <a href="http://twitter.com/matthewmcvickar">Matthew McVickar</a> pointed out a bug in my system for displaying tweets:</p>

<blockquote>
  <p>I’m curious about the background in that embedded tweet of mine. My Twitter profile background is solid blue, and I don’t have a background image set. Looking up one of my tweets with the API, though, I see that my profile_background_image_url variable points to that pixelated background in the embedded tweet. I recognize it as a background image that I used a while ago. It would appear that hitting ‘Remove background’ on the Twitter ‘Design Settings’ page just flips a switch to show the color and not a background image, while the image itself remains attached to the profile. And since background images trump colors in CSS, that’s why my embedded tweet features that old image! Strange.</p>
</blockquote>

<p>Luckily the bug was easy to fix.</p>

<p>My system for displaying embedded tweets is called <a href="https://github.com/drdrang/blackbirdpy"><code>Blackbird.py</code></a>. It&#8217;s my fork of Jeff Miller&#8217;s <a href="https://github.com/jmillerinc/blackbirdpy">Python reimplementation</a> of Robin Sloan&#8217;s original <a href="http://media.twitter.com/blackbird-pie">Blackbird Pie</a>. A few weeks ago I did a <a href="http://www.leancrew.com/all-this/2012/01/new-blackbird-py/">significant rewrite</a>, stealing a few ideas from Twitter&#8217;s own embedded tweet implementation to make tweets look better in the RSS feed. In doing so, I needed to add some JavaScript/jQuery code to the system to get the look I wanted here on ANIAT itself.</p>

<p>As of yesterday, the background was set with the line</p>

<pre><code>$("#" + divID).css('background', 'url(' + data.user.profile_background_image_url + ') #' + data.user.profile_background_color);
</code></pre>

<p>which, for Matthew&#8217;s tweet that I quoted yesterday, is the jQuery equivalent of this CSS:</p>

<pre><code>#t163374655128862720 {
  background: url(https://si0.twimg.com/profile_background_images/15300442/Static.png) #0BAEE5;
}
</code></pre>

<p>(That long string with a <code>t</code> followed by several digits is the <code>id</code> of the <code>&lt;div</code>> that contains the embedded tweet. The digits come from the status ID of the tweet, and the <code>t</code> stands for &#8220;tweet.&#8221;)</p>

<p>If the image is available, it will be displayed. As Matthew discovered, Twitter doesn&#8217;t delete the image when you choose &#8220;Remove background&#8221; from your design settings—it just flips the value of a Boolean variable.</p>

<p>That Boolean variable is called <code>profile_use_background_image</code>, and it&#8217;s available in the <code>user</code> section of the data returned by <a href="https://dev.twitter.com/docs/api/1/get/statuses/show/%3Aid">the <code>statuses/show</code> command</a> of the Twitter API. By changing the JavaScript to</p>

<pre><code>if (data.user.profile_use_background_image == true) {
  $("#" + divID).css('background', 'url(' + data.user.profile_background_image_url + ') #' + data.user.profile_background_color);
}
else {
  $("#" + divID).css('background', '#' + data.user.profile_background_color);
}
</code></pre>

<p>the embedded tweet respects the user&#8217;s choice of background.</p>

<p>The new code is in the <a href="https://github.com/drdrang/blackbirdpy"><code>Blackbird.py</code> GitHub repository</a> and has been installed here. Because it&#8217;s JavaScript and changes the page on the fly, Matthew&#8217;s tweet, which had the wrong background yesterday, has the right background today. Like this:</p>

<div class="bbpBox" id="t163374655128862720a"><blockquote><span class="twContent"><a href="http://twitter.com/drdrang">@drdrang</a> Have you seen this ‘mind-melting’ equation? Seems to be up your alley of amateur math naïveté. <a href="http://blog.untitledmagazine.net/post/16643489845/kateoplis-jtotheizzoe-mind-melter-of-the-day">blog.untitledmagazine.net/post/166434898…</a></span><span class="twMeta"><br /><span class="twDecoration">&nbsp;&nbsp;&mdash; </span><span class="twRealName">Matthew McVickar</span><span class="twDecoration"> (</span><a href="http://twitter.com/matthewmcvickar"><span class="twScreenName">@matthewmcvickar</span></a><span class="twDecoration">) </span><a href="https://twitter.com/#!/matthewmcvickar/status/163374655128862720"><span class="twTimeStamp">Sat Jan 28 2012</span></a><span class="twDecoration"></span></span></blockquote></div>

<p>So now when people read yesterday&#8217;s post and see his comment about a pixelated background image, they&#8217;ll think he&#8217;s nuts. I can live with that.</p>

<div class="update">

<p><strong>Update 1/30/12</strong><br />
Note to self: If you&#8217;re going to embed a tweet twice, and both instances might appear on the same page, make sure you change the <code>id</code> of one of them by hand. Two <code>&lt;div&gt;</code>s with the same <code>id</code> being altered by jQuery can lead to really stupid looking results.</p>

</div>

<div class="update">

<p><strong>Second update 1/30/12</strong><br />
Mr. McVickar&#8217;s wish is apparently my command. Here&#8217;s an another line of JavaScript/jQuery I added to the <code>styleTweets</code> function to change the link color to the tweeter&#8217;s preference.</p>

<pre><code>$("#" + divID + " a").css('color', '#' + data.user.profile_link_color);
</code></pre>

<p>Apart from a couple of things associated with the sidebar, which have no business in an embedded tweet, <code>profile_link_color</code> is the last user setting that comes in response to a call to <a href="https://dev.twitter.com/docs/api/1/get/statuses/show/%3Aid"><code>statuses/show</code></a>. So I should be done tweaking Blackbird.py for a while.</p>

</div>
]]></content:encoded>
			<wfw:commentRss>http://www.leancrew.com/all-this/2012/01/blackbird-py-and-background-images/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Fun with Python&#8217;s decimal library</title>
		<link>http://www.leancrew.com/all-this/2012/01/fun-with-pythons-decimal-library/</link>
		<comments>http://www.leancrew.com/all-this/2012/01/fun-with-pythons-decimal-library/#comments</comments>
		<pubDate>Sun, 29 Jan 2012 03:02:59 +0000</pubDate>
		<dc:creator>Dr. Drang</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[math]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[python]]></category>

		<guid isPermaLink="false">http://www.leancrew.com/all-this/?p=1748</guid>
		<description><![CDATA[You&#8217;ve probably run across a link to this cute math fact in the past few days: [\frac{1}{998001} = 0.000001002\ldots100101102\ldots900901902\ldots999\ldots] This relatively simple fraction generates a decimal number that contains, in order, every three-digit sequence from 000 to 999 (except, sadly, 998). Pretty cool. If it&#8217;s true. I don&#8217;t want to be cynical or disparage the&#8230;]]></description>
			<content:encoded><![CDATA[<p>You&#8217;ve probably run across a link to <a href="http://www.futilitycloset.com/2012/01/08/math-notes-76/">this cute math fact</a> in the past few days:</p>

<span class="MathJax_Preview">[\frac{1}{998001} = 0.000001002\ldots100101102\ldots900901902\ldots999\ldots]</span><script type="math/tex; mode=display">\frac{1}{998001} = 0.000001002\ldots100101102\ldots900901902\ldots999\ldots</script>

<p>This relatively simple fraction generates a decimal number that contains, in order, every three-digit sequence from 000 to 999 (except, sadly, 998). Pretty cool.</p>

<p>If it&#8217;s true. I don&#8217;t want to be cynical or disparage the integrity of the people who&#8217;ve been posting this, but folks on the internet <a href="http://www.leancrew.com/all-this/2010/10/calendrical-stupidity/">don&#8217;t always get their math right</a>. Shocking, I know.</p>

<p>So I was going to see if I could generate the decimal and check, but it slipped my mind until Matthew McVickar (Hawaii&#8217;s latest musical superstar, successor to Don Ho and Israel Kamakawiwoʻole), reminded me of it on Twitter.</p>

<div class="bbpBox" id="t163374655128862720"><blockquote><span class="twContent"><a href="http://twitter.com/drdrang">@drdrang</a> Have you seen this ‘mind-melting’ equation? Seems to be up your alley of amateur math naïveté. <a href="http://blog.untitledmagazine.net/post/16643489845/kateoplis-jtotheizzoe-mind-melter-of-the-day">blog.untitledmagazine.net/post/166434898…</a></span><span class="twMeta"><br /><span class="twDecoration">&nbsp;&nbsp;&mdash; </span><span class="twRealName">Matthew McVickar</span><span class="twDecoration"> (</span><a href="http://twitter.com/matthewmcvickar"><span class="twScreenName">@matthewmcvickar</span></a><span class="twDecoration">) </span><a href="https://twitter.com/#!/matthewmcvickar/statuses/163374655128862720"><span class="twTimeStamp">Sat Jan 28 2012</span></a><span class="twDecoration"></span></span></blockquote></div>

<p>Hmmm… I&#8217;m not sure how to take the &#8220;naïveté&#8221; comment, but I appreciate the diacritical marks.</p>

<p>When you need an answer to 3000 decimal places, you can&#8217;t just open up <a href="http://www.gnu.org/s/octave/">Octave</a> and type in the fraction. Double precision floating point is going to come up about 2983 places short. The easiest way to check the fraction is to enter it in <a href="http://www.wolframalpha.com/input/?i=1%2F998001">Wolfram Alpha</a>. It&#8217;ll start out truncated, but you can keep asking for more digits until you get enough. The problem with Alpha is that the answer isn&#8217;t very nicely formatted.</p>

<p>I decided to try <a href="http://docs.python.org/library/decimal.html">Python&#8217;s <code>decimal</code> library</a>, a library I&#8217;d never used before. It&#8217;s main claim is its ability to represent decimals exactly rather than with the rounding and truncation associated with floating point arithmetic, but it can also handle huge numbers of decimal places.</p>

<p>Here&#8217;s a quick script to print out the decimal in a form that&#8217;s easy to read:</p>

<pre><code>python:
 1:  #!/usr/bin/python
 2:  
 3:  from decimal import *
 4:  from sys import stdout
 5:  
 6:  getcontext().prec = 3000
 7:  num = Decimal(1)
 8:  denom = Decimal(998001)
 9:  dec = str(num/denom)[2:3002]
10:  
11:  for i,c in enumerate(dec):
12:    if i % 30 == 29:
13:      stdout.write(c + "\n")
14:    elif i % 3 == 2:
15:      stdout.write(c + " ")
16:    else:
17:      stdout.write(c)
</code></pre>

<p>Line 6 sets the precision to 3000 decimal places. Lines 7 and 8 set the numerator and denominator using the library&#8217;s <code>Decimal</code> constructor. Line 9 does the division, converts the result to string form, and extracts the 3000 characters after the decimal point. Lines 11-17 print out the result in lines of 30 characters, with spaces between every set of 3. There&#8217;s probably a clever way to get the same output with the <code>format</code> command, but I haven&#8217;t used <code>format</code> enough to be comfortable with it.</p>

<p>Here&#8217;s the result</p>

<pre><code>000 001 002 003 004 005 006 007 008 009
010 011 012 013 014 015 016 017 018 019
020 021 022 023 024 025 026 027 028 029
030 031 032 033 034 035 036 037 038 039
040 041 042 043 044 045 046 047 048 049
050 051 052 053 054 055 056 057 058 059
060 061 062 063 064 065 066 067 068 069
070 071 072 073 074 075 076 077 078 079
080 081 082 083 084 085 086 087 088 089
090 091 092 093 094 095 096 097 098 099
100 101 102 103 104 105 106 107 108 109
110 111 112 113 114 115 116 117 118 119
120 121 122 123 124 125 126 127 128 129
130 131 132 133 134 135 136 137 138 139
140 141 142 143 144 145 146 147 148 149
150 151 152 153 154 155 156 157 158 159
160 161 162 163 164 165 166 167 168 169
170 171 172 173 174 175 176 177 178 179
180 181 182 183 184 185 186 187 188 189
190 191 192 193 194 195 196 197 198 199
200 201 202 203 204 205 206 207 208 209
210 211 212 213 214 215 216 217 218 219
220 221 222 223 224 225 226 227 228 229
230 231 232 233 234 235 236 237 238 239
240 241 242 243 244 245 246 247 248 249
250 251 252 253 254 255 256 257 258 259
260 261 262 263 264 265 266 267 268 269
270 271 272 273 274 275 276 277 278 279
280 281 282 283 284 285 286 287 288 289
290 291 292 293 294 295 296 297 298 299
300 301 302 303 304 305 306 307 308 309
310 311 312 313 314 315 316 317 318 319
320 321 322 323 324 325 326 327 328 329
330 331 332 333 334 335 336 337 338 339
340 341 342 343 344 345 346 347 348 349
350 351 352 353 354 355 356 357 358 359
360 361 362 363 364 365 366 367 368 369
370 371 372 373 374 375 376 377 378 379
380 381 382 383 384 385 386 387 388 389
390 391 392 393 394 395 396 397 398 399
400 401 402 403 404 405 406 407 408 409
410 411 412 413 414 415 416 417 418 419
420 421 422 423 424 425 426 427 428 429
430 431 432 433 434 435 436 437 438 439
440 441 442 443 444 445 446 447 448 449
450 451 452 453 454 455 456 457 458 459
460 461 462 463 464 465 466 467 468 469
470 471 472 473 474 475 476 477 478 479
480 481 482 483 484 485 486 487 488 489
490 491 492 493 494 495 496 497 498 499
500 501 502 503 504 505 506 507 508 509
510 511 512 513 514 515 516 517 518 519
520 521 522 523 524 525 526 527 528 529
530 531 532 533 534 535 536 537 538 539
540 541 542 543 544 545 546 547 548 549
550 551 552 553 554 555 556 557 558 559
560 561 562 563 564 565 566 567 568 569
570 571 572 573 574 575 576 577 578 579
580 581 582 583 584 585 586 587 588 589
590 591 592 593 594 595 596 597 598 599
600 601 602 603 604 605 606 607 608 609
610 611 612 613 614 615 616 617 618 619
620 621 622 623 624 625 626 627 628 629
630 631 632 633 634 635 636 637 638 639
640 641 642 643 644 645 646 647 648 649
650 651 652 653 654 655 656 657 658 659
660 661 662 663 664 665 666 667 668 669
670 671 672 673 674 675 676 677 678 679
680 681 682 683 684 685 686 687 688 689
690 691 692 693 694 695 696 697 698 699
700 701 702 703 704 705 706 707 708 709
710 711 712 713 714 715 716 717 718 719
720 721 722 723 724 725 726 727 728 729
730 731 732 733 734 735 736 737 738 739
740 741 742 743 744 745 746 747 748 749
750 751 752 753 754 755 756 757 758 759
760 761 762 763 764 765 766 767 768 769
770 771 772 773 774 775 776 777 778 779
780 781 782 783 784 785 786 787 788 789
790 791 792 793 794 795 796 797 798 799
800 801 802 803 804 805 806 807 808 809
810 811 812 813 814 815 816 817 818 819
820 821 822 823 824 825 826 827 828 829
830 831 832 833 834 835 836 837 838 839
840 841 842 843 844 845 846 847 848 849
850 851 852 853 854 855 856 857 858 859
860 861 862 863 864 865 866 867 868 869
870 871 872 873 874 875 876 877 878 879
880 881 882 883 884 885 886 887 888 889
890 891 892 893 894 895 896 897 898 899
900 901 902 903 904 905 906 907 908 909
910 911 912 913 914 915 916 917 918 919
920 921 922 923 924 925 926 927 928 929
930 931 932 933 934 935 936 937 938 939
940 941 942 943 944 945 946 947 948 949
950 951 952 953 954 955 956 957 958 959
960 961 962 963 964 965 966 967 968 969
970 971 972 973 974 975 976 977 978 979
980 981 982 983 984 985 986 987 988 989
990 991 992 993 994 995 996 997 999 000
</code></pre>

<p>Formatting the output this way makes it easy to see that the claim is correct. Too bad about the missing 998.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.leancrew.com/all-this/2012/01/fun-with-pythons-decimal-library/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>My life as a Twitter spammer</title>
		<link>http://www.leancrew.com/all-this/2012/01/my-life-as-a-twitter-spammer/</link>
		<comments>http://www.leancrew.com/all-this/2012/01/my-life-as-a-twitter-spammer/#comments</comments>
		<pubDate>Sat, 28 Jan 2012 15:52:15 +0000</pubDate>
		<dc:creator>Dr. Drang</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[twitter]]></category>

		<guid isPermaLink="false">http://www.leancrew.com/all-this/?p=1747</guid>
		<description><![CDATA[Something weird is going on with Twitter&#8217;s shortened links. I don&#8217;t know how it&#8217;s happening, but there must be several people on Twitter who think I&#8217;m using fake accounts to spam them with links to one of my posts. I get many visits from people following links from the t.co domain, Twitter&#8217;s own link shortening&#8230;]]></description>
			<content:encoded><![CDATA[<p>Something weird is going on with Twitter&#8217;s shortened links. I don&#8217;t know how it&#8217;s happening, but there must be several people on Twitter who think I&#8217;m using fake accounts to spam them with links to one of my posts.</p>

<p>I get many visits from people following links from the t.co domain, Twitter&#8217;s own link shortening service. That isn&#8217;t unusual. Like many bloggers, I often tweet a link to a post I&#8217;ve just written. But when a post I <em>didn&#8217;t</em> tweet about starts getting hits from t.co, it means someone else has tweeted a link to me. Obviously, if my Twitter handle is included in the tweet, there&#8217;s no mystery; I&#8217;ll probably already have seen the link in my mentions. Sometimes, though, the link comes without a mention, and if I have the time I try to find out who did it.</p>

<p>Yesterday morning I was home from work, trying to fight off the beginning of a cold (unsuccessfully), so I had the time. I went to twitter.com and searched for &#8220;leancrew.&#8221; Twitter initially shows the &#8220;top&#8221; tweets, using whatever algorithm it has for determining &#8220;top,&#8221; but you can switch to &#8220;all&#8221; tweets from a little popup menu near the top of the page.</p>

<p><a href="http://www.flickr.com/photos/drdrang/6775973115/"><img class="ss" src="http://farm8.staticflickr.com/7017/6775973115_7be2e2a2c6_o.jpg" alt="Tweet search results options" title="Tweet search results options" /></a></p>

<p>When I did that, a bunch of obvious spam appeared in <a href="https://twitter.com/#!/search/realtime/leancrew">the results</a>.</p>

<p><a href="http://www.flickr.com/photos/drdrang/6771178233/"><img class="ss" src="http://farm8.staticflickr.com/7150/6771178233_17d0090d78_z.jpg" alt="Twitter spam in search results" title="Twitter spam in search results" /></a></p>

<p>&#8220;Leancrew&#8221; didn&#8217;t appear in any of these tweets, but by hovering over the link I could see the URL of <a href="http://www.leancrew.com/all-this/2010/05/relative-links-in-pnotes/">one of my posts</a> in the popup.</p>

<p><a href="http://www.flickr.com/photos/drdrang/6772083493/"><img class="ss" src="http://farm8.staticflickr.com/7175/6772083493_2a46ae3ec4_o.jpg" alt="Spam link to me" title="Spam link to me" /></a></p>

<p>WTF? You won&#8217;t be surprised to learn, I&#8217;m sure, that clicking those link takes you not to my post but to a site that purports to be giving away iPads. So why does the little popup show one of my URLs?</p>

<p>My understanding of that popup is that it&#8217;s supposed to show the resolved URL of the link, that is, the URL at the end of the redirect chain. For example, if you just post a regular, direct URL into your tweet, Twitter will turn it into a short t.co link which redirects to the original. That&#8217;s one step of redirection. If, however, you paste a j.mp link into your tweet, Twitter wraps that into a t.co link before posting and you now have two steps of redirection: from t.co to j.mp and then from j.mp to the original site.</p>

<p>Some of this information is included in the <a href="https://dev.twitter.com/docs/tweet-entities">tweet entities</a> section of the tweet. For example, let&#8217;s look at this non-spam tweet:</p>

<div class="bbpBox" id="t163262396130017281"><blockquote><span class="twContent">BoingBoing: Lego Moleskine notebooks <a href="http://j.mp/AlbEFV">j.mp/AlbEFV</a></span><span class="twMeta"><br /><span class="twDecoration">&nbsp;&nbsp;&mdash; </span><span class="twRealName">macdrifter</span><span class="twDecoration"> (</span><a href="http://twitter.com/macdrifter"><span class="twScreenName">@macdrifter</span></a><span class="twDecoration">) </span><a href="https://twitter.com/#!/macdrifter/statuses/163262396130017281"><span class="twTimeStamp">Sat Jan 28 2012</span></a><span class="twDecoration"></span></span></blockquote></div>

<p>By going to Twitter&#8217;s <a href="https://dev.twitter.com/console">Exploring the Twitter API</a> page we can see what this tweet is composed of by <a href="https://dev.twitter.com/docs/api/1/get/statuses/show/%3Aid">issuing a GET</a> on the URL</p>

<pre><code>https://api.twitter.com/1/statuses/show.json?id= 163262396130017281&amp;include_entities=true
</code></pre>

<p>In the JSON that&#8217;s returned, the <code>entities</code> part looks like this:</p>

<pre><code>"entities":  {
   "user_mentions":  [],
   "urls":  [
      {
       "url": "http://t.co/2ukW7OSE",
       "display_url": "j.mp/AlbEFV",
       "indices":  [
         37,
         57
       ],
       "expanded_url": "http://j.mp/AlbEFV"
     }
   ],
   "hashtags":  []
 },
</code></pre>

<p>The <code>url</code> is the t.co-shortened link that Twitter made, the <code>expanded_url</code> is the j.mp URL you pasted into the tweet,<sup id="fnref:client"><a href="#fn:client" rel="footnote">1</a></sup> and <code>display_url</code> is the text that appears in the tweet.</p>

<p>If you look at this tweet on <a href="https://twitter.com/#!/macdrifter/statuses/163262396130017281">its own page</a>, you&#8217;ll see that the popup shows the <code>expanded_url</code>.</p>

<p><a href="http://www.flickr.com/photos/drdrang/6776405591/"><img class="ss" src="http://farm8.staticflickr.com/7165/6776405591_a1f711c6cf_z.jpg" alt="Popup on individual tweet page" title="Popup on individual tweet page" /> </a></p>

<p>But if you look at it in search results or a timeline, the popup shows the resolved URL that j.mp redirects to.</p>

<p><a href="http://www.flickr.com/photos/drdrang/6776156983/"><img class="ss" src="http://farm8.staticflickr.com/7022/6776156983_1643bc2835_z.jpg" alt="Popup in timeline" title="Popup in timeline" /></a></p>

<p>So what does this have to do with the spam tweets I saw? Well, if I GET one of the spams, the <code>entities</code> section is</p>

<pre><code>"entities":  {
  "urls":  [
     {
      "url": "http://t.co/mNL7S05v",
      "display_url": "hi24.info/c1b",
      "expanded_url": "http://hi24.info/c1b",
      "indices":  [
        15,
        35
      ]
    }
  ],
  "user_mentions":  [
     {
      "id_str": "421544542",
      "name": "Sifri y Quique",
      "screen_name": "quiquesifri96",
      "indices":  [
        0,
        14
      ],
      "id": 421544542
    }
  ],
  "hashtags":  []
},
</code></pre>

<p>which is very much like <a href="http://twitter.com/macdrifter">@macdrifter</a>&#8217;s tweet, with hi24.info URLs taking the place of the j.mp URLs.<sup id="fnref:mention"><a href="#fn:mention" rel="footnote">2</a></sup></p>

<p>Somehow, then, when Twitter resolves that hi24.info URL to make the popup, it resolves to my post; but when a user clicks on it in a browser, it resolves to the spam page offering free iPads.</p>

<p>There&#8217;s actually quite a long chain of redirects associated with that hi24.info URL. When I pass it to <a href="http://www.gnu.org/software/wget/manual/wget.html"><code>wget</code></a>, I get this reponse:</p>

<pre><code>$ wget http://hi24.info/c1b
--2012-01-28 09:04:41--  http://hi24.info/c1b
Resolving hi24.info... 173.208.182.10
Connecting to hi24.info|173.208.182.10|:80... connected.
HTTP request sent, awaiting response... 302 Moved Temporarily
Location: http://oldcarreviews.info/index.php?t=1327316945403 [following]
--2012-01-28 09:04:41--  http://oldcarreviews.info/index.php?t=1327316945403
Resolving oldcarreviews.info... 66.85.156.14
Connecting to oldcarreviews.info|66.85.156.14|:80... connected.
HTTP request sent, awaiting response... 302 Moved Temporarily
Location: http://google.com/ [following]
--2012-01-28 09:04:43--  http://google.com/
Resolving google.com... 74.125.225.147, 74.125.225.148, 74.125.225.144, ...
Connecting to google.com|74.125.225.147|:80... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: http://www.google.com/ [following]
--2012-01-28 09:04:44--  http://www.google.com/
Resolving www.google.com... 74.125.225.114, 74.125.225.115, 74.125.225.116, ...
Connecting to www.google.com|74.125.225.114|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
</code></pre>

<p>To me, the weirdest thing is after redirecting to oldcarrierreviews.info, it redirects to google.com (twice). I don&#8217;t understand how a google.com request ends up at a spam page.</p>

<p>Anyway, to bring this rambling post to a close, my guess is that h124.info uses the headers of the HTTP request to figure out whether it&#8217;s getting an inquiry from Twitter or from a normal browser. If it&#8217;s from Twitter, it returns an inoffensive URL (like mine); if it&#8217;s from a browser (or <code>wget</code>), it sets off the chain of redirects that lands at the spam page.</p>

<p>I&#8217;m kind of pissed at this. I don&#8217;t want my domain associated with spam, but I don&#8217;t know how to stop it. It&#8217;s similar to when email spam spoofs your address, but I think most people know by now that email addresses can be spoofed. Twitter&#8217;s stated reason for wrapping all links in t.co redirects is to reduce spam and keep us safe. In this case, not only is the spam getting through (the <a href="https://twitter.com/#!/bandurazqoeb3">@bandurazqoeb3</a> and <a href="https://twitter.com/#!/klughadmq0">@klughadmq0</a> accounts are still active as I write this), but the users getting these spams would be justified in thinking I have something to do with them.</p>

<p>I tweeted a complaint to Twitter&#8217;s <a href="https://twitter.com/#!/spam">@spam</a> account but got no response. When I&#8217;m done posting this, I&#8217;ll go through the steps in <a href="https://support.twitter.com/forms/security">this security form</a> to report the issue. But I don&#8217;t see my career as an inadvertent Twitter spammer ending soon.</p>

<div class="footnotes">
<hr />
<ol>

<li id="fn:client">
<p>Or maybe you pasted in a long URL and your Twitter client turned it into a j.mp link for you. The point is that Twitter received the j.mp URL.&#160;<a href="#fnref:client" rev="footnote">&#8617;</a></p>
</li>

<li id="fn:mention">
<p>And of course the spam has a user mention—it wouldn&#8217;t be Twitter spam without that.&#160;<a href="#fnref:mention" rev="footnote">&#8617;</a></p>
</li>

</ol>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.leancrew.com/all-this/2012/01/my-life-as-a-twitter-spammer/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Photo location service (and Bing)</title>
		<link>http://www.leancrew.com/all-this/2012/01/photo-location-service-and-bing/</link>
		<comments>http://www.leancrew.com/all-this/2012/01/photo-location-service-and-bing/#comments</comments>
		<pubDate>Sat, 28 Jan 2012 03:18:41 +0000</pubDate>
		<dc:creator>Dr. Drang</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[bing]]></category>
		<category><![CDATA[google]]></category>
		<category><![CDATA[mac]]></category>
		<category><![CDATA[map]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[services]]></category>

		<guid isPermaLink="false">http://www.leancrew.com/all-this/?p=1746</guid>
		<description><![CDATA[Following up on this morning&#8217;s post, I&#8217;ve expanded my map script to include an option for using Bing Maps to display the location of a photograph, and I&#8217;ve created a Service so I can get a map by right-clicking on a photo in the Finder and choosing &#8220;Photo location&#8221; from the contextual menu. Here&#8217;s the&#8230;]]></description>
			<content:encoded><![CDATA[<p>Following up on <a href="http://www.leancrew.com/all-this/2012/01/locating-your-photos-on-google-maps/">this morning&#8217;s post</a>, I&#8217;ve expanded my <code>map</code> script to include an option for using Bing Maps to display the location of a photograph, and I&#8217;ve created a Service so I can get a map by right-clicking on a photo in the Finder and choosing &#8220;Photo location&#8221; from the contextual menu.</p>

<p>Here&#8217;s the updated <code>map</code> script:</p>

<pre><code>python:
 1:  #!/usr/bin/python
 2:  
 3:  import pyexiv2
 4:  import sys
 5:  import subprocess
 6:  import getopt
 7:  
 8:  usage = """Usage: map [option] file
 9:  
10:  Options:
11:    -b    use Bing instead of Google Maps
12:    -h    show this help message
13:  
14:  Get the GPS data from the given image file and open a map to that
15:  location in the user's default browser."""
16:  
17:  # Parse the options.
18:  try:
19:    options, photos = getopt.getopt(sys.argv[1:], 'bh')
20:  except getopt.GetoptError, err:
21:    print str(err)
22:    sys.exit(2)
23:  
24:  # Set the option values.
25:  engine = 'google'           # default
26:  for o, a in options:
27:    if o == '-b':
28:      engine = 'bing'
29:    else:
30:      print usage
31:      sys.exit()
32:  
33:  try:
34:    # Open the photo file.
35:    md = pyexiv2.ImageMetadata(photos[0])
36:    md.read()
37:  except:
38:    print usage
39:    sys.exit()
40:  
41:  try:
42:    # Read the GPS info.
43:    latref = md['Exif.GPSInfo.GPSLatitudeRef'].value
44:    lat = md['Exif.GPSInfo.GPSLatitude'].value
45:    lonref = md['Exif.GPSInfo.GPSLongitudeRef'].value
46:    lon = md['Exif.GPSInfo.GPSLongitude'].value
47:  except KeyError:
48:    print "No GPS data for %s" % photos[0]
49:    sys.exit(1)
50:  
51:  # Convert the latitude and longitude to signed floating point values.
52:  latitude = float(lat[0]) + float(lat[1])/60 + float(lat[2])/3600
53:  longitude = float(lon[0]) + float(lon[1])/60 + float(lon[2])/3600
54:  if latref == 'S': latitude = -latitude
55:  if lonref == 'W': longitude = -longitude
56:  
57:  # Construct the Google Maps or Bing Maps query and open it.
58:  if engine == 'bing':
59:    query = "http://www.bing.com/maps/?v=2&amp;where1=%.6f,%.6f" % (latitude, longitude)
60:  else:
61:    query = "http://maps.google.com/maps?q=loc:%.6f,%.6f" % (latitude, longitude)
62:  subprocess.call(['open', query])
</code></pre>

<p>It&#8217;s quite a bit longer than before, mostly because of the option handling code in Lines 17-31 and the usage message in Lines 8-15. I&#8217;ve also separated the error handling into two <code>try/except</code> clauses, one for dealing with errors in opening the image file and the other for handling files with no location. (Note also that Line 49 has <code>map</code> return a nonzero result code if there&#8217;s no GPS info. We&#8217;ll use that in the service.)</p>

<p>Otherwise, it&#8217;s basically the same script. Use</p>

<pre><code>map photo.jpg
</code></pre>

<p>to open a Google Maps view of the photo&#8217;s location in your default browser. Use</p>

<pre><code>map -b photo.jpg
</code></pre>

<p>to open the location in Bing Maps instead.</p>

<p>The &#8220;Photo location&#8221; service is defined this way in Automator:</p>

<p><a href="http://www.flickr.com/photos/drdrang/6773829741/"><img class="ss" src="http://farm8.staticflickr.com/7151/6773829741_98434bc1d0_z.jpg" alt="Photo location service" title="Photo location service" /></a></p>

<p>It&#8217;s defined only for image files selected in the Finder and runs the following shell script:</p>

<pre><code>bash:
1:  for f in "$@"
2:  do
3:    ~/bin/map "$f"
4:    if [ $? -ne 0 ]; then
5:     say "No location"
6:    fi
7:  done
</code></pre>

<p>For every selected image file, it runs the <code>map</code> program. If the <code>map</code> command in Line 3 returns a nonzero result code, <code>$?</code>—which it will if the image file has no location metadata—the script will say &#8220;No location&#8221; in the user&#8217;s default voice. If you want to use this service, but find the talking a bit too cute, you can change Line 5 to</p>

<pre><code>bash:
5:     osascript -e "beep 1"
</code></pre>

<p>and it will sound the system alert if the file has no GPS data. If you&#8217;d rather use Bing, slip a <code>-b</code> after the <code>map</code> in Line 3. Or you could make two services, one that uses Google and one that uses Bing.</p>

<p>With these improvements, I think I&#8217;m done. I have a tool that works the way I want on my computers. I could rewrite <code>map</code> to use an EXIF library that&#8217;s easier to install than <a href="http://tilloy.net/dev/pyexiv2/"><code>pyexiv2</code></a>, possibly switching to Perl or Ruby in the process, but since I managed to get <code>pyexiv2</code> installed on my machines, I&#8217;d get no benefit out of doing so. But if someone else takes this code and rewrites if for another library, I&#8217;d love to hear about it.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.leancrew.com/all-this/2012/01/photo-location-service-and-bing/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Locating your photos on Google Maps</title>
		<link>http://www.leancrew.com/all-this/2012/01/locating-your-photos-on-google-maps/</link>
		<comments>http://www.leancrew.com/all-this/2012/01/locating-your-photos-on-google-maps/#comments</comments>
		<pubDate>Fri, 27 Jan 2012 15:21:04 +0000</pubDate>
		<dc:creator>Dr. Drang</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[google]]></category>
		<category><![CDATA[map]]></category>
		<category><![CDATA[photo]]></category>
		<category><![CDATA[productivity]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[python]]></category>

		<guid isPermaLink="false">http://www.leancrew.com/all-this/?p=1745</guid>
		<description><![CDATA[Back in October I wrote a little script that added GPS location information to photos. My idea was to be able to take one photo with my iPhone, which would capture the location in its EXIF metadata, and use the script to transfer that information to all the photos I took with my regular camera&#8230;]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.leancrew.com/all-this/2011/10/location-location-location/">Back in October</a> I wrote a little script that added GPS location information to photos. My idea was to be able to take one photo with my iPhone, which would capture the location in its EXIF metadata, and use the script to transfer that information to all the photos I took with my regular camera at that same location. It works well, and I&#8217;ve been using it ever since, but once I had a bunch of photos with location info in them, I needed a tool that would do the converse: show me where they were taken. It turned out to be very easy to write.</p>

<p>The October program is called <code>coordinate</code>, and I use it like this:</p>

<pre><code>coordinate -g iphone-photo.jpg IMG*
</code></pre>

<p>This reads the GPS metadata from the <code>iphone-photo.jpg</code> file and writes it to all the files that start with <code>IMG</code>. The new program is called <code>map</code>, and when I call it,</p>

<pre><code>map photo.jpg
</code></pre>

<p>it reads the GPS info from the image file and opens Google Maps in my browser with a marker at the photo&#8217;s location.</p>

<p><a href="http://www.flickr.com/photos/drdrang/6770915445/"><img class="ss" src="http://farm8.staticflickr.com/7015/6770915445_4b4365d34d_z.jpg" alt="Photo location in Google Maps" title="Photo location in Google Maps" /></a></p>

<p>Here&#8217;s the code for <code>map</code>:</p>

<pre><code>python:
 1:  #!/usr/bin/python
 2:  
 3:  import pyexiv2
 4:  import sys
 5:  import subprocess
 6:  
 7:  try:
 8:    # Open the photo file.
 9:    photo = sys.argv[1]
10:    md = pyexiv2.ImageMetadata(photo)
11:    md.read()
12:  
13:    # Read the GPS info.
14:    latref = md['Exif.GPSInfo.GPSLatitudeRef'].value
15:    lat = md['Exif.GPSInfo.GPSLatitude'].value
16:    lonref = md['Exif.GPSInfo.GPSLongitudeRef'].value
17:    lon = md['Exif.GPSInfo.GPSLongitude'].value
18:  
19:  except:
20:    print "No GPS info in file %s" % photo
21:    sys.exit()
22:  
23:  # Convert the latitude and longitude to signed floating point values.
24:  latitude = float(lat[0]) + float(lat[1])/60 + float(lat[2])/3600
25:  longitude = float(lon[0]) + float(lon[1])/60 + float(lon[2])/3600
26:  if latref == 'S': latitude = -latitude
27:  if lonref == 'W': longitude = -longitude
28:  
29:  # Construct the Google Maps query and open it.
30:  query = "http://maps.google.com/maps?q=loc:%.6f,%.6f" % (latitude, longitude)
31:  subprocess.call(['open', query])
</code></pre>

<p>It uses the standard <code>sys</code> and <code>subprocess</code> libraries and the distinctly non-standard <a href="http://tilloy.net/dev/pyexiv2/"><code>pyexiv2</code> library</a>. Installing <code>pyexiv2</code> isn&#8217;t the hardest thing in the world, but it takes more than a simple <code>pip pyexiv2</code>. I have <a href="http://www.leancrew.com/all-this/2011/10/homebrew-and-pyexiv2/">a writeup on how to do it via Homebrew</a> if you&#8217;re interested.</p>

<p>The script is easy to read, I think. Lines 9-17 get the name of the file from the command line, open it, and extract the GPS information. If there&#8217;s a problem in any of those steps, an exception is raised, and Lines 20-21 print an error message and quit.</p>

<p>Because the latitude and longitude are returned in an odd format (a list of three <a href="http://docs.python.org/library/fractions.html">Fractions</a>, one each for degrees, minutes, and seconds), Lines 24-27 are needed to put them in a format we can use to query Google Maps. The query is then constructed in Line 30, and Line 31 sends it to OS X&#8217;s wonderfully useful <a href="http://developer.apple.com/library/mac/#documentation/Darwin/Reference/Manpages/man1/open.1.html"><code>open</code> command</a> to be opened in the default browser. The Google Maps API automatically drops a marker at the location and centers the map around the marker.</p>

<p>I could extend the query with <a href="http://mapki.com/wiki/Google_Map_Parameters">more parameters</a> to set the zoom level, force the display to satellite, map, or street view, and so on, but I prefer to stick with the defaults.</p>

<p>There are some obvious improvements I could make:</p>

<ul>
<li>Add an option to use Bing instead of Google Maps. With aerial photos, Bing&#8217;s oblique bird&#8217;s eye view is often more useful than Google&#8217;s top view. I haven&#8217;t looked yet, but I assume Bing can be queried through the URL just like Google Maps can.</li>
<li>Rewrite it for a library that&#8217;s easier to install than <code>pyexiv2</code>. This wouldn&#8217;t affect me, but would make it easier for others to use.</li>
<li>Turn it into a Service. This is a trivial change that would produce a big improvement in usability. With a Service, I could just right-click on an image in the Finder and get the location by choosing an item from the popup menu.</li>
</ul>

<p>If you&#8217;re wondering why I don&#8217;t just use iPhoto&#8217;s built-in mapping tools, it&#8217;s because I don&#8217;t use iPhoto. I find it very clunky and slow, and its organization system doesn&#8217;t work for the photos I take for my job (which is most of my photos). The <code>map</code> script gives me a quick way to check the location of any photo, regardless of where it&#8217;s stored on my computer.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.leancrew.com/all-this/2012/01/locating-your-photos-on-google-maps/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Old problem, new trick</title>
		<link>http://www.leancrew.com/all-this/2012/01/old-problem-new-trick/</link>
		<comments>http://www.leancrew.com/all-this/2012/01/old-problem-new-trick/#comments</comments>
		<pubDate>Fri, 27 Jan 2012 04:49:50 +0000</pubDate>
		<dc:creator>Dr. Drang</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[iphone]]></category>
		<category><![CDATA[jotnot]]></category>
		<category><![CDATA[mac]]></category>
		<category><![CDATA[numbers]]></category>
		<category><![CDATA[productivity]]></category>

		<guid isPermaLink="false">http://www.leancrew.com/all-this/?p=1744</guid>
		<description><![CDATA[I went on a business trip to upstate New York this week and tried out a new system for keeping track of my expenses. It&#8217;s simple enough that I might actually stick with it. There are a bunch of expense tracking apps for the iPhone. Most of them either require you to open some sort&#8230;]]></description>
			<content:encoded><![CDATA[<p>I went on a business trip to upstate New York this week and tried out a new system for keeping track of my expenses. It&#8217;s simple enough that I might actually stick with it.</p>

<p>There are a bunch of expense tracking apps for the iPhone. Most of them either require you to open some sort of web-based account, which I didn&#8217;t want for privacy reasons, or ask you to enter a bunch of information on your phone, which I didn&#8217;t want for tedium reasons. After looking things over, I decided to combine a few things I use already: <a href="http://itunes.apple.com/us/app/jotnot-scanner-pro/id307868751?mt=8&amp;partnerId=30&amp;siteID=L4JhWyGwYTM">JotNot Scanner Pro</a>, email, and a Numbers expense report template.</p>

<p>I wrote about JotNot <a href="http://www.leancrew.com/all-this/2011/10/jotnot/">back in October</a>. It&#8217;s a simple app that uses your iPhone camera as a scanner. What makes it different from the standard Camera app is that it crops, straightens, corrects for perspective, and equalizes the tone of your photos.</p>

<p><a href="http://www.flickr.com/photos/drdrang/6241344400/"><img class="ss" src="http://farm7.static.flickr.com/6058/6241344400_4a0415c629_z.jpg" alt="JotNot" title="JotNot" /></a></p>

<p>It turns out that this, combined with its ability to email the adjusted image, is all I really need.</p>

<p>In the past, I&#8217;d collect receipts during my trip and staple them to my expense report, which I&#8217;d make out when I got back to the office. This was pretty simple and hard to beat for efficiency, although I did have to be careful to keep my receipts together until the trip was over. If a client wanted a copy of my receipts, I&#8217;d lay them out on our scanner and turn them into a PDF that I could email along with the invoice. This was the part I hated. My company&#8217;s scanner, although networked, can&#8217;t be run from my Mac. I have to save the scans to a Windows machine and then log on to that computer from mine to get at them. If the scanner messes up—OK, if <em>I</em> mess up in using the scanner—I have to go back and forth between offices to redo it.</p>

<p>Scanning receipts in JotNot and mailing them to myself turns out to be really convenient. By default, JotNot puts its name in the text of the email, so when I get back to the office, I just search for &#8220;JotNot&#8221; in my inbox and drag the various receipts out of their mail messages and into my Numbers expense report.</p>

<p>There&#8217;s not much to my expense report. It&#8217;s just a simple Numbers template, a single sheet with two tables on it—one for the date of the report and one for the expenses themselves. I&#8217;m not a big fan of Numbers, but the ability to lay out separate tables on the same sheet works nicely for this kind of document. The date of the report doesn&#8217;t have to be regimented into the same grid structure as the expense details.</p>

<p><a href="http://www.flickr.com/photos/drdrang/6769004371/"><img class="ss" src="http://farm8.staticflickr.com/7175/6769004371_9410eeec6a_z.jpg" alt="Numbers expense template" title="Numbers expense template" /></a></p>

<p>As I said, I drag the emailed receipts out of Mail and into Numbers, putting them on separate pages so they don&#8217;t block the table. The Show Print View command from the View menu is handy for making sure the edges of the receipts don&#8217;t leak over onto other pages. I print out a copy for our bookkeeper to use when comes in to cut our expense checks and &#8220;print&#8221; another copy as a PDF to email to the client along with the invoice.</p>

<p>Numbers allows you to rotate and resize images, so it&#8217;s easy to squeeze several receipts onto a single page. One thing I don&#8217;t like, and don&#8217;t understand, is that images dragged into Number always appear initially at the upper left corner of the first page, not where I &#8220;let go&#8221; of them. So after dragging them into Numbers, I have to drag them around within Numbers to get them where I want. This could be improved.<sup id="fnref:09"><a href="#fn:09" rel="footnote">1</a></sup></p>

<p>There is, obviously, not much to my expense report template, but if you want to use it as a starting point to make one of your own, you can <a href="http://leancrew.com/all-this/downloads/drang-expense.zip">download a zip file of it</a>. After expanding it, just double-click on the file to open it in Numbers. Make the changes you need, then choose Save As Template… from the File menu. It&#8217;ll available to choose from the template picker whenever you want to make a new spreadsheet.</p>

<p><a href="http://www.flickr.com/photos/drdrang/6769181725/"><img class="ss" src="http://farm8.staticflickr.com/7028/6769181725_3b87396b8e_z.jpg" alt="Numbers template picker" title="Numbers template picker" /></a></p>

<div class="footnotes">
<hr />
<ol>

<li id="fn:09">
<p>It&#8217;s possible that it <em>has</em> been improved. I&#8217;m using Numbers ’08, which is one behind the latest version. I&#8217;d be happy to upgrade, but the current version is ’09, and I refuse to upgrade to something that&#8217;s already 3 years old. <a href="https://twitter.com/#!/MacSparky/statuses/86818940596129793">Like David Sparks</a>, I feel certain the whole iWorks suite is on the verge of getting a new version. The problem is we&#8217;ve both been certain about it for months and Apple still hasn&#8217;t come through.&#160;<a href="#fnref:09" rev="footnote">&#8617;</a></p>
</li>

</ol>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.leancrew.com/all-this/2012/01/old-problem-new-trick/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Layers</title>
		<link>http://www.leancrew.com/all-this/2012/01/layers/</link>
		<comments>http://www.leancrew.com/all-this/2012/01/layers/#comments</comments>
		<pubDate>Wed, 25 Jan 2012 02:19:43 +0000</pubDate>
		<dc:creator>Dr. Drang</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[browsers]]></category>

		<guid isPermaLink="false">http://www.leancrew.com/all-this/?p=1743</guid>
		<description><![CDATA[I liked just about everything about this Tim Bray post from a couple of days ago. It&#8217;s called &#8220;Browser Sedimentation,&#8221; and it&#8217;s about the layers of non-content (sometimes called &#8220;chrome&#8221;) that have built up in today&#8217;s browsers. Here&#8217;s what my Safari looks like most of the time: Title bar, tool bar, bookmarks bar, tab bar.&#8230;]]></description>
			<content:encoded><![CDATA[<p>I liked just about everything about <a href="http://www.tbray.org/ongoing/When/201x/2012/01/22/Confusing-Browser">this Tim Bray post</a> from a couple of days ago. It&#8217;s called &#8220;Browser Sedimentation,&#8221; and it&#8217;s about the layers of non-content (sometimes called &#8220;chrome&#8221;) that have built up in today&#8217;s browsers. Here&#8217;s what my Safari looks like most of the time:</p>

<p><img class="ss" src="http://farm8.staticflickr.com/7014/6757908859_5fb64c8359_z.jpg" alt="Safari layers" title="Safari layers" /></p>

<p>Title bar, tool bar, bookmarks bar, tab bar. The main difference between this browser sediment and river sediment is that these layers accumulate at the top instead of the bottom.<sup id="fnref:status"><a href="#fn:status" rel="footnote">1</a></sup></p>

<p>The one thing I sort of disagree with is this:</p>

<blockquote>
  <p>The problem is that people like me (and I bet most readers here) can’t even see that there’s a jumble; the sediments of infrastructure are clearly separated in our understanding and thus our eyes. But occasionally I get a flash of how it must look to civilian eyes, and it doesn’t make me happy.</p>
</blockquote>

<p>As one of his commenters, <a href="http://www.tbray.org/ongoing/When/201x/2012/01/22/Confusing-Browser#c1327335044.973919">Ben Meadowcroft</a>, said, the browsers of non-expert users usually have even more layers. I was reminded of this bit from <a href="http://www.mcsweeneys.net/articles/in-which-i-fix-my-girlfriends-grandparents-wifi-and-am-hailed-as-a-conquering-hero">Mike Lacher&#8217;s epic tale from McSweeney&#8217;s</a> on fixing a non-expert&#8217;s computer:</p>

<blockquote>
  <p>With a resounding click he opened Internet Explorer 6 and gazed deep into its depths, past the Yahoo toolbar, the MSN toolbar, the Ask.com toolbar, and the AOL toolbar. And then did he see, at long last, that The Google did load.</p>
</blockquote>

<p>My mom&#8217;s computer has some awful, branded version of Internet Explorer running on it, and the toolbars take up so much of the window it feels like you&#8217;re looking at the web between the slats of Venetian blinds. But she likes all that crap. When I&#8217;ve suggested getting rid of some of the tool bars, she&#8217;s been horrified; it wouldn&#8217;t be her familiar web experience without them.</p>

<p>I&#8217;ve always thought this was the real story behind the software that John Gruber highlights in his occasional <a href="http://www.google.com/search?client=safari&amp;rls=en&amp;q=%22user+interface+of+the+week%22+site:daringfireball.net&amp;ie=UTF-8&amp;oe=UTF-8">User Interface of the Week</a> posts. It&#8217;s not so much that the designers of these applications have no taste, it&#8217;s that their users have no taste. There&#8217;s something about an ugly, cluttered user interface that says &#8220;computer&#8221; to the great majority of users out there.</p>

<div class="footnotes">
<hr />
<ol>

<li id="fn:status">
<p>Except for the status bar, which I forgot to mention.&#160;<a href="#fnref:status" rev="footnote">&#8617;</a></p>
</li>

</ol>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.leancrew.com/all-this/2012/01/layers/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
	</channel>
</rss>

