I license the engine to clients, but the WhirlyViz app is free and the best demonstration of the engine itself.
Javascript
WhirlyViz is eminently configurable. That's kind of the point, but I wasn't entirely satisfied with what we had in 1.2. Now I know why. With version 1.3 we've switched to Javascript.
Apple added support for JavaScriptCore in ios7. They put together a fairly nice bridge between Objective-C and Javascript to go with it. This gives you a clean, empty Javascript interpreter to use in your app.
That's just what I did. WhirlyViz configuration files are now Javascript. Whenever a user taps on something, or a remote vector tile needs to be loaded, or even when the visualization starts up you're running a Javascript function.
This example talks to a WMS server.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Polluting the global name space with spherical mercator extents | |
var xMin = -20037508.34; | |
var yMin = -20037508.34; | |
var xMax = 20037508.34; | |
var yMax = 20037508.34; | |
var xSpan = xMax - xMin; | |
var ySpan = yMax - yMin; | |
// Current center of the queries | |
var centerLon = -75.20896911621094; | |
var centerLat = 40.024459635387906; | |
// Where we store parameter data for the tiles | |
var transitInfo = new Object(); | |
// Called by the image tile loader on a random thread for every tile we need to load | |
// We just return the URL | |
var tileurl = function(x,y,level) | |
{ | |
num = 1 << level; | |
cellX = xSpan / num; | |
cellY = ySpan / num; | |
tileXmin = xMin + x * cellX; | |
tileXmax = xMin + (x + 1.0) * cellX; | |
tileYmin = yMin + y * cellY; | |
tileYmax = yMin + (y + 1.0) * cellY; | |
// console.log(level + ": (" + x + "," + y + ")" + "size: (" + cellX + "," + cellY + ")"); | |
url = "http://transit.geotrellis.com/api/travelshed/wms?service=WMS&request=GetMap&version=1.1.1&layers=&styles=&format=image/jpeg&transparent=false&height=256&width=256&latitude=" + centerLat + "&longitude=" + centerLon + "&time=" + transitInfo.time + "&duration=3600&modes=" + transitInfo.mode + "&schedule=" + transitInfo.date + "&direction=departing&breaks=600,900,1200,1800,2400,3000,3600,4500,5400,7200&palette=0xF68481,0xFDB383,0xFEE085,0xDCF288,0xB6F2AE,0x98FEE6,0x83D9FD,0x81A8FC,0x8083F7,0x7F81BD&srs=EPSG:3857&bbox=" + tileXmin + "," + tileYmin + "," + tileXmax + "," + tileYmax; | |
// console.log("Fetching tile: " + url); | |
return url; | |
} | |
// Note: No reason to make these global | |
var curLon = 0.0; | |
var curLat = 0.0; | |
// This is the transportation distance layer | |
var transLayer = null; | |
var resetTransLayer = function() | |
{ | |
// Tear down the old transportion layer | |
if (transLayer) | |
transLayer.remove(); | |
// Transportation overly with an active tile URL callback | |
transLayer = wviz.addImageTileLayer( | |
{ | |
name: "transit layer", | |
cache: false, | |
flipy: true, | |
coordSys: "EPSG:3857", | |
minZoom: 10, | |
maxZoom: 20, | |
drawPriority: 10, | |
alpha: 0.75, | |
tileURLFunc: tileurl | |
}); | |
} | |
// Called when the user taps at a location. Called on the main thread so don't block. | |
wviz.events.onTap = function(lon,lat) | |
{ | |
} | |
// Called when the user taps and hold on a location. Called on the main thread. | |
wviz.events.onPress = function(lon,lat) | |
{ | |
centerLon = lon; | |
centerLat = lat; | |
// Center changes so reload everything | |
resetTransLayer(); | |
} | |
// Called when the app view is first initialized | |
wviz.events.onStartup = function() | |
{ | |
// Background color | |
wviz.setBackgroundColor("#FFFFFFFF"); | |
// Name up top | |
wviz.setTitle("GeoTrellis Transit: Philadelphia"); | |
// Legend on the lower left | |
wviz.setLegend("<html><body style=\"background-color=black;font-size:18;text-align:center;\"><b style=\"color:#F48380\">0m</b> <b style=\"color:#FAB282\">10m</b> <b style=\"color:#FDDF84\">15m</b> <b style=\"color:#DCF288\">20m</b> <b style=\"color:#B6F2AE\">30m</b> <b style=\"color:#98FEE6\">40m</b> <b style=\"color:#83D9FD\">50m</b></body></html>","#000000AA"); | |
// Background layer with a map | |
backLayer = wviz.addImageTileLayer( | |
{ | |
tileJson: "http://a.tiles.mapbox.com/v3/azavea.map-zbompf85.json", | |
minZoom: 0, | |
maxZoom: 22, | |
drawPriority: 0 | |
}); | |
// Bike or walk control | |
wviz.addControl( | |
{ | |
name: "transitType", | |
"display name": "Walk or Bike", | |
type: "list", | |
"default": "Bike", | |
"initial index": 0, | |
"values":[ | |
"Walk", | |
"Bike" | |
] | |
}); | |
// Regional rail | |
wviz.addControl( | |
{ | |
name: "regionalRail", | |
"display name": "Regional rail", | |
type: "list", | |
"default": "No", | |
"initial index": 0, | |
"values":[ | |
"No", | |
"Yes" | |
] | |
}); | |
// Bus & Subway | |
wviz.addControl( | |
{ | |
name: "busSubway", | |
"display name": "Bus & Subway", | |
type: "list", | |
"default": "No", | |
"initial index": 0, | |
"values":[ | |
"No", | |
"Yes" | |
] | |
}); | |
// Day of week or weekend control | |
wviz.addControl( | |
{ | |
"name":"Date", | |
"display name":"Date", | |
"type":"list", | |
"default":"Bike", | |
"initial index":0, | |
"values":[ | |
"Weekday", | |
"Saturday", | |
"Sunday" | |
] | |
}); | |
// Time of day control | |
wviz.addControl( | |
{ | |
"name": "Time", | |
"display name": "Departure Time", | |
"type": "time", | |
"default": "09:00:00", | |
"min": "00:00:00", | |
"max": "23:30:00" | |
}); | |
// Call the config routine to set defaults | |
wviz.events.onConfig(); | |
} | |
// Called when the controls are edited and changed. Called on the main thread | |
wviz.events.onConfig = function() | |
{ | |
// console.log("onConfig: mode = " + wviz.env.transitType + " date = " + wviz.env.Date + " time = " + wviz.env.Time); | |
// Pull data out of the config | |
transitInfo.mode = null; | |
switch (wviz.env.transitType) | |
{ | |
case "Walk": | |
transitInfo.mode = "walking"; | |
break; | |
case "Bike": | |
transitInfo.mode = "biking"; | |
break; | |
} | |
switch (wviz.env.busSubway) | |
{ | |
case "No": | |
break; | |
case "Yes": | |
transitInfo.mode += ",bus"; | |
break; | |
} | |
switch (wviz.env.regionalRail) | |
{ | |
case "No": | |
break; | |
case "Yes": | |
transitInfo.mode += ",train"; | |
break; | |
} | |
transitInfo.date = null; | |
switch (wviz.env.Date) | |
{ | |
case "Weekday": | |
transitInfo.date = "weekday"; | |
break; | |
case "Saturday": | |
transitInfo.date = "saturday"; | |
break; | |
case "Sunday": | |
transitInfo.date = "sunday"; | |
break; | |
} | |
vals = wviz.env.Time.split(":"); | |
transitInfo.time = (vals[0] * 60 + +vals[1]) * 60 + +vals[2]; | |
// Change or setup the transportation layer | |
resetTransLayer(); | |
} | |
// We need to set the globe or map type here before anything gets run | |
wviz.settings = { | |
"map type":"map2d", | |
"start":{ | |
"lon":-75.20, | |
"lat":40.02, | |
"height":0.01 | |
}, | |
"info url":"" | |
}; | |
// Let the startup routine know we're happy | |
true; |
It's incredibly flexible and as long as it's not in the main rendering loop (good grief no!) still very fast. There are other advantages too.
Everything Speaks JSON
These days every remote web service speaks JSON. [Except for the ones that don't]. Using Javascript makes it much easier to decode a random JSON return value and do something visual with it.
![]() |
SF Bay Area Bike Share |
For example, several public bike share companies provide JSON feeds with up-to-the-minute station availability. They have spatial data, but not as GeoJSON. That's kind of annoying, but now easily fixed.
- We fetch the appropriate feed in the script
- Parse the JSON return and iterate through the records
- Pull out location data and anything we might want to display
- Convert the data to GeoJSON and hand it back to WhirlyViz
WhirlyViz still knows nothing about the JSON feeds from the bike share companies, it's just displaying GeoJSON with some styling info. The script has all the smarts and, best of all, it wasn't all that much code.
Running Your Own Script
WhirlyViz has its own URL scheme for displaying data. Refer back to this post for details.
Now you can run your own Javascript configuration file too. Just specify it like so.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
whirlyviz://script?js=http://foo.bar.com/myscript.js |
The script argument tells WhirlyViz we're running a script and the js argument tells it where to go get it. Of course, then you have to write one of these and the documentation is... lacking.
Wrapup
You can see some of the new examples in the latest WhirlyViz update. I'm now talking to two new geospatial data services which will get their own blog posts.
I'm very happy with this approach and I'll be writing up the Javascript functions shortly. You can kind of figure it out from the examples, but documentation is important.
In the mean time, if you have a neat visualization you'd like to see on mobile, hit me up and we'll talk.
No comments:
Post a Comment