Monthly Archives: June 2015


I’ve been tinkering around with the ArcGIS JS API. This example pulls a KMZ file from the Prince George Open Data Catalogue, displays it on a map, and requests/displays a 1km buffer around the features. The tricky part was installing/configuring a proxy page to allow for the convoluted buffer request.

Disclaimer: for a better example, see here for loading a KML and buffering the features.

Lego Area

legoCBC reported that Lego will spend 1 billion Danish kroner ($185.4 million CAD) to come up with a more sustainable material to replace acrylonitrile butadiene styrene. The figure that really caught my eye is that Lego produces more than 5,000,000 pieces per hour.

They do not get into what “a piece” means, so I’m going to assume that means the plain-old 2×4 block that we all know and love (keep in mind that the average piece is likely smaller than a 2×4, although I believe this is the most common piece). A 2×4 measures 15.8 x 31.8mm (from here). At 5,000,000 pieces per hour, that would be 2512.2 sq.m. Or, 60293 sq. m per day. Or, 22,006,872 sq. m per year. That’s 22 sq. km!

You can use this map to visualize it for yourself. Feel free to drag the circles to your city.

3-Letter Word Scatterplot


Here‘s a D3-powered scatterplot showing the distribution of 3-letter words, at least, according to

Instructions are simple: chose the starting letter in the dropdown list. Then, find individual words by the second letter (y-axis) and third letter (x-axis). Mouseover the point to convince yourself.

OpenWeatherMap IDW

idwOpenWeatherMap has a nice API that allows you to consume live weather data. I have made other simple examples here (v2.1) and here (v2.0 – doesn’t work), but you should check out the OWM API documentation and examples for more.

This example (v2.5) collects weather info from several stations across British Columbia and Alberta, interpolates temperature values between the stations using the Inverse Distance Weighting method, and displays both the stations (points) and interpolated surface (raster) as PaperJS objects.

 <!DOCTYPE html>
 Code by Darren Wiens
 <style type="text/css">
 html { height: 100% }
 #map { height: 500px; width: 500px; }
 canvas { position: absolute; top: 10; left: 10; z-index: 1; pointer-events:none; }
 <!-- Load the Paper.js library -->
 <script type="text/javascript" src=""></script>
 <script type="text/javascript" src=""></script>
 <script src=""></script>
 <!-- Define inlined PaperScript associate it with myCanvas -->
 <script type="text/paperscript" canvas="myCanvas">
 var maxWidth = 500;
 var maxHeight = 500;
 var maxPoint = new Point(maxWidth,maxHeight);
var rect = new Path.Rectangle(0,0,maxWidth, maxHeight);
 rect.strokeColor = 'black';
 rect.strokeWidth = 3;
var map;
 var sampleCirs = new Group();
 var texts = new Group();
 var interpRaster = new Raster('myImg');
 var minTemp = 1000;
 var maxTemp = -1000;
function onFrame(event)
function pointToLatLng(point)
 var proj = map.getProjection();
 var bounds = map.getBounds();
 var ne = bounds.getNorthEast();
 var sw = bounds.getSouthWest();
 var neWorldXY = proj.fromLatLngToPoint(ne);
 var swWorldXY = proj.fromLatLngToPoint(sw);
 var curPixelX = point.x / Math.pow(2,map.getZoom());
 var curPixelY = point.y / Math.pow(2,map.getZoom());
 var curWorldX = curPixelX + swWorldXY.x;
 var curWorldY = curPixelY + neWorldXY.y;
 var curWorldPoint = new google.maps.Point(curWorldX,curWorldY);
 var curLatLng = proj.fromPointToLatLng(curWorldPoint);
 return curLatLng;
function latLngToPoint(latLng)
 var proj = map.getProjection();
 var calWorldPoint = proj.fromLatLngToPoint(latLng);
 var calPixelPointx = calWorldPoint.x * Math.pow(2,map.getZoom());
 var calPixelPointy = calWorldPoint.y * Math.pow(2,map.getZoom());
 var bounds = map.getBounds();
 var ne = bounds.getNorthEast();
 var sw = bounds.getSouthWest();
 var neWorldPoint = proj.fromLatLngToPoint(ne);
 var swWorldPoint = proj.fromLatLngToPoint(sw);
 var ePixelPoint = neWorldPoint.x * Math.pow(2,map.getZoom());
 var nPixelPoint = neWorldPoint.y * Math.pow(2,map.getZoom());
 var wPixelPoint = swWorldPoint.x * Math.pow(2,map.getZoom());
 var sPixelPoint = swWorldPoint.y * Math.pow(2,map.getZoom());
 var screenPixelX = calPixelPointx - wPixelPoint;
 var screenPixelY = calPixelPointy - nPixelPoint;
 var point = new Point(screenPixelX, screenPixelY);
 return point;
function drawPoints(s)
 childCount = 0;
 for (var station=0;station<s.length;station++) {
 if (s[station].last.hasOwnProperty('main')) {
 if (s[station].last.main.hasOwnProperty('temp')) {
 var lat = s[station];
 var lng = s[station].station.coord.lon;
 var newPoint = new Point(latLngToPoint(new google.maps.LatLng(lat, lng)));
 sampleCirs.addChild(new Path.Circle(newPoint,2));
 sampleCirs.children[childCount].value = s[station].last.main.temp - 273.15;
 sampleCirs.children[childCount].latlng = pointToLatLng(sampleCirs.children[childCount].position);
 if (sampleCirs.children[childCount].value < minTemp) {
 minTemp = sampleCirs.children[childCount].value;
 if (sampleCirs.children[childCount].value > maxTemp) {
 maxTemp = sampleCirs.children[childCount].value;
 sampleCirs.strokeColor = 'black';
 sampleCirs.fillColor = 'black';
 var text = new PointText(newPoint + new Point(3,-3));
 text.content = Math.round(sampleCirs.children[childCount].value);
 text.latlng = pointToLatLng(text.position);
function drawRaster()
 interpRaster.size = new Size(100,100);
 for (var i=0;i<100;i++)
 for (var j=0;j<100;j++)
 var wj = 0;
 var wis = [];
 for (var k=0;k<sampleCirs.children.length;k++)
 var dx = sampleCirs.children[k].position.x - (i * 5);
 var dy = sampleCirs.children[k].position.y - (j * 5);
 var dk = Math.sqrt(Math.pow(dx,2) + Math.pow(dy,2));
 var p = 3;
 var wj_inst = 1/(Math.pow(dk,p))
 wj += wj_inst;
 var u = 0;
 for (var l=0;l<wis.length;l++)
 u += wis[l]/wj;
 var scale = (u-minTemp)/(maxTemp-minTemp);
 interpRaster.setPixel(i,j,new RgbColor(scale,0,1-scale,1));
 interpRaster.opacity = 0.7;
 interpRaster.latlng = map.getCenter();
 interpRaster.TLlatlng = pointToLatLng(interpRaster.bounds.topLeft);
 interpRaster.BRlatlng = pointToLatLng(interpRaster.bounds.bottomRight);
 console.log("MAX: " + maxTemp);
 console.log("MIN: " + minTemp);
function getData(s){
$(document).ready(function() {
 var style = [
 { "elementType": "geometry.fill",
 "stylers": [ { "visibility": "off" } ]
 },{ "featureType":
 "elementType": "geometry.fill",
 "stylers": [ { "visibility": "on" },
 { "color": "#000000" } ]
 },{ "featureType": "water",
 "elementType": "geometry.fill",
 "stylers": [ { "color": "#000000" } ]
 } ]
var myLatlng = new google.maps.LatLng(53.917, -122.75);
 var myOptions = {
 zoom: 5,
 center: myLatlng,
 mapTypeId: google.maps.MapTypeId.ROADMAP,
 disableDefaultUI: true
 map = new google.maps.Map(document.getElementById("map"), myOptions);
 map.setOptions({styles: style});
google.maps.event.addListener(map, 'projection_changed', function() {
 $.getJSON('', getData); // API v.2.5
google.maps.event.addListener(map, 'center_changed', function() {
 for (var i=0;i<sampleCirs.children.length;i++)
 sampleCirs.children[i].position = latLngToPoint(sampleCirs.children[i].latlng);
 sampleCirs.children[i].latlng = pointToLatLng(sampleCirs.children[i].position);
 texts.children[i].position = latLngToPoint(texts.children[i].latlng);
 texts.children[i].latlng = pointToLatLng(texts.children[i].position);
 interpRaster.position = latLngToPoint(interpRaster.latlng);
 interpRaster.latlng = pointToLatLng(interpRaster.position);
google.maps.event.addListener(map, 'zoom_changed', function() {
 for (var i=0;i<sampleCirs.children.length;i++)
 sampleCirs.children[i].position = latLngToPoint(sampleCirs.children[i].latlng);
 sampleCirs.children[i].latlng = pointToLatLng(sampleCirs.children[i].position);
 texts.children[i].position = sampleCirs.children[i].position + new Point(3,-3);
 texts.children[i].latlng = pointToLatLng(texts.children[i].position);
 var tl = latLngToPoint(interpRaster.TLlatlng);
 var br = latLngToPoint(interpRaster.BRlatlng);
 var bounds = new Rectangle(tl,br);
 interpRaster.TLlatlng = pointToLatLng(interpRaster.bounds.topLeft);
 interpRaster.BRlatlng = pointToLatLng(interpRaster.bounds.bottomRight);
 <canvas id="myCanvas" width="500" height="500"></canvas>
 <div id="map" width="500" height="500"></div>
 <img id="myImg"></img>

Extract & Project Data from Image

lightningEnvironment Canada has a cool mapping project: the Canadian Lightning Danger Map. The main issue I have with it is that the maps aren’t interactive (you can’t pan, zoom, etc.), so I set out to make them so.

My initial thought was simple: overlay the image on Google Maps tiles. Download or reference the images from the EC website, create and drape a Ground Overlay, and that would be it. The URLs for the most recent hour or so of maps can be constructed thusly:

where PAC = pacific (other regions are also available), and a timestamp, 201506142030: June 14, 2015 8:30pm. Tragically, the EC maps are projected, while Google Maps (and essentially every other web map tile scheme) are in Web Mercator – they will not simply overlay.

So, I needed to somehow project the data from the projected map into lat/long coordinates to plot on a web map. First step, figure out the extent of one of the maps, which I managed by georeferencing in ArcMap. Luckily, the maps for the Pacific Region appear to be in NAD83 BC Environment Albers (wkid:3005), so when georeferenced, it sits exactly straight on the screen. The metadata tells me the extent and pixel size for the image. The maps show lightning danger by colouring pixels red(ish). So, the plan is now: load the image, look at all the pixels in the image, and if it’s red, figure out the BC Albers coordinates by the pixel position relative to the image extent, convert to lat/long, and plot on a web map. Phew!

In the end, this isn’t as hard as it sounds. Loading and inspecting the image data is relatively straightforward using a canvas element’s getImageData method. This allows us to determine if a pixel is reddish. Unfortunately, this was where my master plan came crumbling down. I wanted to simply reference recent images from the EC server, but you cannot use getImageData across domains without EC’s server being CORS enabled, which it isn’t. For the purpose of this project, I’ve downloaded one image and placed it within my own domain so I can access the image data. By the pixel position, we can do some simple math to convert the position to BC Albers coordinates.

Figuring out an automated way to project coordinates was a serious stumbling block. I had never paid much attention to the ArcGIS JSAPI, but with it, you can create a (or use ESRI‘s) geometry service to project points from one coordinate system to another.

Yay! I’m all set. Here is my map which inspects an EC lightning danger map (on the right) and plots points in the correct position on a web mercator-based web map (on the left). Now you can navigate around the lightning danger shapes to your heart’s content! Next step, somehow convince EC to enable CORS on their server…

Gmaps: Activity Logger

1I’ve been thinking about logging web map user interactions for a while, heavily inspired by sparkgeo‘s Maptiks product. If you haven’t tried Maptiks, you should. The Maptiks dashboard provides you with lots of interesting factoids like map loads, bounce rate, load times, and a heatmap of where users are looking, stratified by zoom level. I have no doubt that this is more than enough data for most people.

With that said, and no offense intended to sparkgeo who I imagine are too busy to address feature requests, the feature I feel that’s missing from Maptiks is that you can’t actually download user interaction data for further analysis. This map, which coincidentally uses Maptiks, is somewhat of a proof of concept towards that goal. The map on the left is what would be presented to the user. When the user navigates, that information (specifically, map center coordinates) is sent to a Firebase (a cloud-based database-type thing), and displayed on the map on the right. I’ve limited the Firebase to only store about 200 points, but that could be expanded to collect many more interactions, although I’m not sure at what point performance would be crippled. I assume a dedicated server would handle the storage of these interactions with little effort. Click and zoom activities could be similarly captured.

Have fun!

edit: I’ll also add that, as with most Firebase apps, the data updates in real time, so you may notice others using the map at the same time as you!