Opera intermission

I’m a long-time Opera fan and flagwaver, but the pain of bugs in 9.5 beta finally forced me to switch to Firefox last week. I’ve been using 9.5 beta1 since the end of October. When do we get a new one? I see the weekly builds, but they’re all alphas (no thanks). The biggest problems I’ve had were with the rich text editor and Flash (which everyone incorrectly assumes to be a cross-browser development panacea), but, as a webdev, I’m now spoiled having instant access to Firebug.

Firefox now has closed tab access (though I miss ctrl-z) and session restore, but within a few minutes I had mouse gestures and wand back. This is petty, but a big reason I loved Opera is that it let me put the address bar, tabs and everything at the bottom. I haven’t found an easy way to do that in FF. FF is also noticeably slower in a lot of actions, but having everything actually work (including Google Docs) is really a breathe of fresh air.

I’ll continue to test Opera and give each beta/full release a whirl, but winning me back will require these to work:

New Minify version in the works

Back in August I came across Ryan Grove‘s great Minify project, which is essentially a PHP client-optimizing file server for Javascript and CSS. I was in the design stage of a project with similar goals so I decided to try merging the two projects. Ryan thought the result had potential to be the base of the next major release, and since he hadn’t had much time lately to devote to Minify, he made me co-owner of the project.

Last week I finally got around to committing the code, and since then I’ve been fleshing it out, adding more features, docs, and tests, and updating the project pages a bit. The major difference is that the server is now built from separate utility classes, each lazy-loaded as needed. Some new features:

  • separate front controller makes it easy to change how HTTP requests are handled
  • file caching uses the very mature Cache_Lite
  • supports conditional GET and far-future Expires models of client caching
  • serve content from any source, not just files
  • new “minifiers” (content compressors) for CSS and HTML with unit tests

At this point to see the new Minify in action you’ll need a Subversion client, like the Windows shell extension TortoiseSVN or the Eclipse plugin Subclipse. You can checkout a read-only working copy at http://minify.googlecode.com/svn/trunk/ and the README has easy setup instructions.

Marriage Rock I

Yeah, we did it. I was in charge of the music and spent most hours from weeks before up until the morning of with my head stuck inside MediaMonkey. I can’t say enough good things about the “Gold” version, but these posts will shockingly not be about the boring machinations of software.

Look For Me As You Go By – The Innocence Mission
Our first dance. I was warned it seems like an eternity when 60 people are staring at you, so I chopped 19 seconds from the end chorus. Love this song.

Keep Sending Me Black Fireworks (wedding edit) – Of Montreal
Lately this is the closest Of Montreal’s come to a straight up pop love song, and it’s perfect. I did cut some weirdness to keep guests bouncing.

Uncontrollable Boasting
Three awesome tunes that pretty much glued themselves together. “Going Back to Miami” is courtesy of the ever-awesome Bubblegum Machine.

I forgot how uninviting makeshift reception dance floors can be, and how quick people file out once the cake is cut, but no matter; these songs became our soundtrack for weeks before and after the ceremony.

A lot more to come…

Distance and bearings between GPS coordinates in SQL

Another post from my sql abominations series. I’m running this on MySQL 5 particularly.

Assume you have a table of locations with Latitude and Longitude for each one. In my case the table is “station”, primary key being “LocID”. With help from this article, first we create a view to get 3D coordinates (6378 = Earth’s radius in km):

CREATE VIEW gpsGlb AS
    SELECT
        LocID
        ,6378 * COS(RADIANS(Latitude)) * COS(RADIANS(Longitude)) AS x
        ,6378 * COS(RADIANS(Latitude)) * SIN(RADIANS(Longitude)) AS y
        ,6378 * SIN(RADIANS(Latitude)) AS z
    FROM station;

Now we can query for great-circle distance (I want rounded miles) to all locations from, say, LocID 405:

SELECT
    LocID
    ,ROUND((2 * 6378 * ASIN(d / 2 / 6378)) * 0.621371192) AS dist_mi
FROM
    (SELECT
        SQRT(dx * dx + dy * dy + dz * dz) AS d
        ,LocID
     FROM
        (SELECT
            p1.x - p2.x AS dx
            ,p1.y - p2.y AS dy
            ,p1.z - p2.z AS dz
            ,p2.LocID
        FROM gpsGlb p1
        JOIN gpsGlb p2 ON (p1.LocID = 405 AND p2.LocID != 405)
       ) t1
    ) t2
ORDER BY dist_mi

With help from this article, we can query for the initial bearing to each location. The “boxed” calculation will come in handy later.

SELECT
    LocID
    ,(360 + DEGREES(ATAN2(y, x))) % 360 AS initBearing_deg
    ,ROUND(((360 + DEGREES(ATAN2(y, x))) % 360) / 22.5) * 22.5
     AS initBearingBoxed_deg
FROM
    (SELECT
        SIN(RADIANS(s2.Longitude - s1.Longitude)) * COS(RADIANS(s2.Latitude))
        AS y
        ,COS(RADIANS(s1.Latitude)) * SIN(RADIANS(s2.Latitude))
            - SIN(RADIANS(s1.Latitude)) * COS(RADIANS(s2.Latitude))
               * COS(RADIANS(s2.Longitude - s1.Longitude))
        AS x
        ,s2.LocID
    FROM station s1
    JOIN station s2 ON (s1.LocID = 405 AND s2.LocID != 405)
    ) q1

What you’ve all been waiting for! The combined query plus boxed compass directions (like ‘NNE’), etc. I’ve also added a limit for the distance in the qq1 subquery since I only want close locations.

SELECT
    qq2.LocID
    ,dist_mi
    ,CASE initBearingBoxed_deg
        WHEN 22.5 THEN 'NNE'   WHEN 45 THEN 'NE'
        WHEN 67.5 THEN 'ENE'   WHEN 90 THEN 'E'
        WHEN 112.5 THEN 'ESE'  WHEN 135 THEN 'SE'
        WHEN 157.5 THEN 'SSE'  WHEN 180 THEN 'S'
        WHEN 202.5 THEN 'SSW'  WHEN 225 THEN 'SW'
        WHEN 247.5 THEN 'WSW'  WHEN 270 THEN 'W'
        WHEN 292.5 THEN 'WNW'  WHEN 315 THEN 'NW'
        WHEN 337.5 THEN 'NNW'  ELSE 'N'
     END AS bearing
FROM (
    SELECT
        LocID
        ,ROUND((2 * 6378 * ASIN(d / 2 / 6378)) * 0.621371192) AS dist_mi
    FROM
        (SELECT
            SQRT(dx * dx + dy * dy + dz * dz) AS d
            ,LocID
         FROM
            (SELECT
                p1.x - p2.x AS dx
                ,p1.y - p2.y AS dy
                ,p1.z - p2.z AS dz
                ,p2.LocID
            FROM gpsGlb p1
            JOIN gpsGlb p2 ON (p1.LocID = 405 AND p2.LocID != 405)
           ) t1
        ) t2
    ) qq1
JOIN (
    SELECT
        LocID
        ,(360 + DEGREES(ATAN2(y, x))) % 360 AS initBearing_deg
        ,(360 + ROUND((DEGREES(ATAN2(y, x))) / 22.5) * 22.5) % 360
         AS initBearingBoxed_deg
    FROM
        (SELECT
            SIN(RADIANS(s2.Longitude - s1.Longitude)) * COS(RADIANS(s2.Latitude))
             AS y
            ,COS(RADIANS(s1.Latitude)) * SIN(RADIANS(s2.Latitude))
                - SIN(RADIANS(s1.Latitude)) * COS(RADIANS(s2.Latitude))
                   * COS(RADIANS(s2.Longitude - s1.Longitude))
             AS x
            ,s2.LocID
        FROM station s1
        JOIN station s2 ON (s1.LocID = 405 AND s2.LocID != 405)
        ) q1
    ) qq2 ON (qq1.LocID = qq2.LocID
              AND qq1.dist_mi <= 60)
ORDER BY dist_mi

Result set is something like:

LocID dist_mi bearing
250 25 E
260 30 NNE
240 42 ENE

Hope this is useful to someone.

Awesome Holiday boredom

88

I got: A, ABBR, ACRONYM, APPLET, AREA, B, BASE, BASEFONT, BIG, BLOCKQUOTE, BODY, BR, BUTTON, CAPTION, CENTER, CITE, CODE, COL, COLGROUP, DD, DEL, DFN, DIR, DIV, DL, DT, EM, FIELDSET, FONT, FORM, FRAME, FRAMESET, H1, H2, H3, H4, H5, H6, HEAD, HR, HTML, I, IFRAME, INPUT, INS, ISINDEX, KBD, LABEL, LEGEND, LI, LINK, MAP, MENU, META, NOFRAMES, NOSCRIPT, OBJECT, OL, OPTGROUP, OPTION, P, PARAM, PRE, Q, S, SAMP, SCRIPT, SELECT, SMALL, SPAN, STRIKE, STRONG, STYLE, SUB, SUP, TABLE, TBODY, TD, TEXTAREA, TFOOT, TH, THEAD, TITLE, TR, TT, U, UL, and VAR

I forgot: ADDRESS, BDO, and IMG. Yes, IMG.

To be fair, this was probably the 8th try. It helps to group them into forms, block/inline, quoting, embedding/linking, framing, lists, data/code display, etc.

Anti-forward ammunition

Gently inform a loved one that forwarding hoaxes and chain letters is a waste of everyone’s time.

I just got this. Get it to everyone fast as possible!

>> Important! Pass it on....
>>
>>> THIS IS NOT A JOKE! I confirmed it on Snope!!!! Please forward this 
>>> to everyone you know! In order to reach us mroe quickly the 
>>> government chose to release this message in e-mail form and you are 
>>> lucky to get it! The major news sources will not air this story until 
>>> tomorrow.
>>> Rick
>>>

>>>> * * *
>>>>
>>>> Washington, D.C. - The President has just declared a state of 
>>>> emergency due to the proliferation of a dangerous new strain of 
>>>> e-mail virus. Now believed to have been created by Iranian 
>>>> terrorists, the Scientific Advisory Panel concluded that the virus 
>>>> is now included in every hoax and chain letter circulated on the 
>>>> Internet. The content of these messages are believed to cause 
>>>> temporary hypnosis, placing the reader in a zombie-like state, in 
>>>> order to coerce the victim to redistribute the message without 
>>>> investigating its veracity or utility. Victims in quarantine were 
>>>> observed infecting dozens more colleagues and family members, in 
>>>> each case exposing the private e-mail addresses of all past victims. 
>>>> Due to the virus's logarithmic growth, estimators have estimated 
>>>> loss of time alone in the billions of American-hours.
>>>>
>>>> Research found that with each new victim the message grew in length. 
>>>> The President stated the many right brackets and exclamation points 
>>>> in the messages add to their "hypnoticness" and urged Americans to 
>>>> look out for these signs and delete the dangerous messages 
>>>> immediately. Also released was the Internet address of a new 
>>>> government site informing the public of five new US laws enacted by 
>>>> Congress yesterday regarding future handling of e-mail:
>>>>
>>>> http://www.netmanners.com/5-rules-for-forwarding-email.html
>>>>
>>>> All Americans are urged to visit the above sight and be on the 
>>>> lookout for any suspicious messages.
>>>>
>>>> White House staff
>>>> Official US governement
>>>>
>>>> * * *
>>>
>>>
>>
>>
> 
>

That URL again is http://www.netmanners.com/5-rules-for-forwarding-email.html.

NPR music + Stephin Merritt

NPR just launched their new music site, which nicely gathers music stories and media from a lot of different NPR shows. My favorite feature: Their media player is Flash-based. It’s a little quirky in Opera, at least, but so much better than the works-some-of-the-time WMP plugin that Opera used, and don’t get me started on Real.

Stephin Merritt was invited to be the first (at least I can’t find any others) participant in All Songs Considered’s “Project Song“. They gave two days use of a studio and engineer and asked him to create a song based on a photograph and phrase. They have video of the process and song, which turned out pretty good. It’s loop-based like those on The House of Tomorrow, but with less noise and reverb (please bring back the noise and reverb). The song’s subject might make a good villian for Lemony Snicket.

Portal

Check out the trailer to Portal. It’s a first-person puzzler where your only ability is to create circular space/time portals between two locations. Your movement (and gravity!) does the rest. The trailer is also pretty funny.

This would’ve made Berzerk so much easier.

LoadVars implements HTTP caching

Searching for info about Flash’s caching mechanism turned up endless posts on how to prevent caching, but none mentioned how LoadVars.load() handled server-sent Cache-Control headers. So I tested this myself.

In the SWF, I loaded the same URL once every 5 seconds using setInterval() and LoadVars.

The URL ran a PHP script that sent content along with Last-Modified and ETag headers based on its own mtime, and the header Cache-Control: max-age=0, private, must-revalidate. This basically means “browsers may cache this item, but must always check the server for updates before using it.”

It worked! Once Flash got those headers, load() would then make “conditional” GET requests: Flash included If-None-Match and If-Modified-Since headers, allowing PHP to respond with 304 Not Modified, and Flash used its cached copy of the data.

Flash seems to use the browser as a proxy to handle these requests and manage the cache, because Flash in Safari 3 shared a limitation of that browser, namely not supporting ETags; Safari only sends back an If-Modified-Since header for conditional GETs.

Please no more Red Book CDs

The Red Book standard defined how audio was to be encoded on a CD, and it was great for 1980, but it, well, kinda sucks now.

1. The error correction is too minimal to withstand real-world abuse (cars, friends, etc.).
2. Tracking is pretty loose, and a lot of players have trouble seeking to the exact beginning of a track. And, of course, skipping is a drag.
3. Uncompressed PCM isn’t space efficient for audio.
4. Ripping is painfully slow if you want it done well, thanks to the first two.

A better (if not ideal) solution is obvious: compressed files on CD-ROM. The CD-ROM standard has a lot more error correction and tracking data built-in, and the space eaten by that that data is minimal compared to what can be saved through modern compression.

For the audiophiles, start with FLACs, then fill the remaining half of the disc with a few grades of mp3s, or license-free formats. Order the folders by increasing compression ratio so that, when you pop it in a player, you get the highest quality your player will support.