5 Lines Holiday Code-off Challenge

Guest Post by Alex Brooker

This blog post started as a “how to” guide for our new GeoJSON flight data API but quickly turned into a competition – firstly over who got to review my code and secondly as to who could create a more efficient / better / cleaner example. Anyway, from my side, this example was a joy to create – mostly because I don’t get the time to create software much anymore, secondly because I got to collaborate technically with some of the technical team. As I settled into pulling this example together I had the distinct feeling of settling into a comfortable chair.

My goal.

Create a map with aircraft tracks using Laminar Data GeoJSON Flight Data API.

Stretch goal.

Complete in 5 lines of code.

Prerequisites

  • Laminar API key (which you can get for free here, 5000 free hits per month easily covers some regular R&D fun).
  • Web browser with a javascript engine
  • (Optional) Postman REST plugin for web browser
  • Notepad++
  • Internet connection

I started by pulling together two supporting Javascript components, Leaflet, and a Leaflet plugin by Calvin Metcalf to help make Ajax calls to fetch GeoJSON. So I started and it immediately felt wrong to write HTML by hand (and it is), I also decided that there was no way I could include the HTML tags in the 5 lines “count”. I needed some rules:

What counts

  • Lines to create essential elements of the user interface
  • Lines to create layers, widgets and for gathering & transforming data
  • Lines to display or filter the data

What doesn’t count

  • HTML basic tags for minimal page construction
  • Include lines for scripts, css
  • Supporting javascript components such as leaflet, plugins, jquery etc.
  • Supporting files such as images and SVG files

Grey area

  • Code that should be in a separate component
  • Anything nice that I add, like popups
  • Putting everything on a single line vs optimising lines using in line functions etc.

The first few lines… which don’t count.

<html>
    <head>
        <link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.2/dist/leaflet.css" />
        <script src="https://unpkg.com/leaflet@1.0.2/dist/leaflet.js"></script>
        <script src="static/lib/leaflet.ajax.min.js"></script>

I started with the GeoJSON sample from the Leaflet documentation, and quickly added a div tag and some javascript to create a map (centred on Southampton) with a base layer from mapbox.com, I also added a nice link to the Laminar Terms of Service & EULA which appears at the bottom right of the map.

<body>
    <div id="mapid" style="width: 600px; height: 400px;"></div>
<script>
    var mymap = L.map('mapid').setView([50.9515, -1.3577], 10);
    L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpandmbXliNDBjZWd2M2x6bDk3c2ZtOTkifQ._QA7i5Mpkd_m30IGElHziw', {
        maxZoom: 18,
        attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' +
                                                '<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' +
                                                'Imagery © <a href="http://mapbox.com">Mapbox</a>, ' +
                                                'Flight Data © <a href="https://developer.laminardata.aero/admin/account/termsofservice/">Laminar Data</a>',
                                id: 'mapbox.streets'
    }).addTo(mymap);

Then, using my GeoJSON Ajax plugin, I added a call to the Laminar Flight Data GeoJSON API. I wanted a vector tile for the South of the UK, so I used the Laminar documentation to pick a grid square…… 3,3

tile_map-e1982769742b758b

Here is the RESTful call with my API key blanked out, tested in the browser or using postman first – https://api.laminardata.aero/v1/tiles/3/3/1/flights?user_key=XXXXXX&format=json

Here is the call in the AJAX plugin and a few lines (which do count) to add the layer to the map.

    var geojsonLayer = new L.GeoJSON.AJAX("https://api.laminardata.aero/v1/tiles/3/3/1/flights?user_key=XXXXXX&format=json",{
        style: function (feature) { return feature.properties && feature.properties.style;},
        pointToLayer: pointToLayer,
        onEachFeature: onEachFeature
        });
    geojsonLayer.addTo(mymap);
</script>
</body>
</html>

I realised that hard coding my user key was a bad idea, so I added a function to get this from the URL, and I updated my RESTful calls.

    function getUrlParameter(name) {
        name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
        var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), results = regex.exec(location.search);
        return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
    }
    var user_key = getUrlParameter('user_key');

I was nearly there, now I needed some plane style icons and if I make them SVG, a rotation based on track heading is possible. A colleague helped me out on both those fronts and I added in a nice rotated marker and SVG icons.  This pulled in a few extra dependencies but right now I didn’t care – I wanted my rotated icons!

            L.RotatedMarker = L.Marker.extend({
                options: {
                    angle: 0
                },
                _setPos: function (pos) {
                    L.Marker.prototype._setPos.call(this, pos);
                    if (L.DomUtil.TRANSFORM) {
                        // use the CSS transform rule if available
                        this._icon.style[L.DomUtil.TRANSFORM] += ' rotate(' + this.options.angle + 'deg)';
                        this._icon.style.transformOrigin = 'center';
                    } else if(L.Browser.ie) {
                        // fallback for IE6, IE7, IE8
                        var rad = this.options.angle * (Math.PI / 180),
                                costheta = Math.cos(rad),
                                sintheta = Math.sin(rad);
                        this._icon.style.filter += ' progid:DXImageTransform.Microsoft.Matrix(sizingMethod=\'auto expand\', M11=' +
                                costheta + ', M12=' + (-sintheta) + ', M21=' + sintheta + ', M22=' + costheta + ')';
                    }
                }
            });
            L.rotatedMarker = function (pos, options) {
                return new L.RotatedMarker(pos, options);
            };

The result

5linesfirstpic

Success! Now I wanted more, lets make popups and show some feature properties! I updated my onEachFeature function to bind some text to each feature.

                function onEachFeature(feature, layer) {
                                var popupContent = "";
                                if (feature.properties && feature.properties.callsign) {
                                                popupContent += "<p>Callsign: ";
                                                popupContent += feature.properties.callsign;
                                                popupContent += "</p>";
                                }
                                if (feature.properties && feature.properties.airline) {
                                                popupContent += "<p>Airline: ";
                                                popupContent += feature.properties.airline;
                                                popupContent += "</p>";
                                }
                                if (feature.properties && feature.properties.arrival) {
                                                if(feature.properties.arrival.aerodrome)  {
                                                                if(feature.properties.arrival.aerodrome.scheduled)          {
                                                                                popupContent += "<p>Arrival: ";
                                                                                popupContent += feature.properties.arrival.aerodrome.scheduled;
                                                                                popupContent += "</p>";
                                                                }
                                                }
                                }
                                layer.bindPopup(popupContent);
                }

The result

blog-entry

My Internal Retro

What went well?

  • Grabbing the Laminar API was easy, adding the basic data to a map was trivial and I could use plugins and examples to get started right away.
  • Collaborating with the engineers and developers at Snowflake is always fun, they especially enjoy reviewing code created by “managers”.

What was weird or unhelpful?

  • I’m used to C++, Java and pure SWIM XML – this Javascript and JSON stuff is wild, fast and weird.

What didn’t work well?

  • I couldn’t stop myself adding features to my client even though the basic map with aircraft was finished after approximately 20 minutes and 5 lines of code (with my rather generous rules).

The complete files are on my github, once reviewed and optimised by the professional Snowflaker developers I’m sure they will appear on the Laminar Data Github as a “proper” sample.

The challenge.

Think you can do better? Take the 5 lines challenge and display more data in a better way. Submit your entry to info@laminardata.aero (along with your language choice and your code). The winner will be announced in the New Year and will be awarded with the coveted “5 Line of Code Holiday Challenge 2016 Winner” hoodie!

holidaycodeoffchallengepicture