This post will demonstrate how to draw polygons on Google Maps v3 using geojson-encoded data from GeoDjango. The most common method for displaying polygons on Google Maps seems to be by using KML. Google Maps requires the KML-file to be available on a public website though and that is kind of a bore for debugging. This approach uses only json and the standard maps drawing API. To get the display the polygons, you have to loop over them and do a setMap() with your map.
I’m assuming the browser is getting geojson from from a GeoDjango model object like this: model.area.geojson, but it should work for data from other sources. Note that I’m using the very excellent Underscore.js Javascript library to do really terse functional programming. Also note that the function takes an optional bounds object which gets expanded as polygon points are added.
The code seems to perform very well in modern browsers, even for fairly large and complex polygons. Unfortunately there is no live demo, but the site I’m working on should go up soon.
function createPolygons(areajson, bounds){
var coords = areajson.coordinates;
var polygons = _(coords).reduce([], function(memo_n, n) {
var polygonpaths = _(n).reduce(new google.maps.MVCArray(), function(memo_o, o) {
var polygoncords = _(o).reduce(new google.maps.MVCArray(), function(memo_p, p) {
var mylatlng = new google.maps.LatLng(p[1], p[0]);
if(bounds){
bounds.extend(mylatlng);
}
memo_p.push(mylatlng);
return memo_p;
});
memo_o.push(polygoncords);
return memo_o;
});
var polygon = new google.maps.Polygon({
paths: polygonpaths,
strokeColor: "#808080",
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: "#C0C0C0",
fillOpacity: 0.35
});
memo_n.push(polygon);
return memo_n;
});
return polygons;
}
I really like the way Javascript is moving from being this annoying thing you have to deal with when doing web-development to becoming a proper server-side programming languages with standard libraries and fast VMs. Yesterday I cloned the Narwhalgit repo and tried to create my own package with a few simple collection types. Narwhal is done by the guys at 280north and conforms to the CommonJS standard, an attempt at a cross-platform standard by the various Javascript platform implementors.
I haven’t quite figured out what goes into creating a proper package, but if you check out and configure Narwhal, you can dump the collection implementation in /lib and the tests in /tests and see it all work. First the collections:
// -- friism Michael Friis
exports.Stack = function() {
return new stack();
};
exports.Queue = function() {
return new queue();
};
/**
* Stack implementation, using native Javascript array
*/
function stack() {
this.data = [];
};
stack.prototype.pop = function() {
return this.data.pop();
};
stack.prototype.push = function(o) {
this.data.push(o);
};
/**
* Queue implementation. Mechanics lifted from Wikipedia
* Could be optimised to do fewer slices, see here:
* http://safalra.com/web-design/javascript/queues/Queue.js
*/
function queue() {
this.data = [];
this.length = 0;
}
queue.prototype.isEmpty = function() {
return (this.data.length == 0);
};
queue.prototype.enqueue = function(obj) {
this.data.push(obj);
this.length = this.data.length;
}
queue.prototype.dequeue = function() {
var ret = this.data[0];
this.data.splice(0,1);
this.length = this.data.length;
return ret;
}
queue.prototype.peek = function() {
return this.data[0];
}
queue.prototype.clear = function() {
this.length = 0;
this.data = [];
}
… and the tests:
var assert = require("test/assert");
var js5 = require("js5");
exports.testStackSimple = function() {
var mystack = new js5.Stack();
var myobject = "1";
mystack.push(myobject);
var popped = mystack.pop();
assert.isEqual(myobject, popped);
};
exports.testStack = function() {
var mystack = new js5.Stack();
for(var i = 99; i >= 0; i--) {
mystack.push(i.toString());
}
for(var j = 0; j < 100; j++) {
assert.isEqual(mystack.pop(), j.toString());
}
}
exports.testQueue = function() {
var myqueue = new js5.Queue();
for(var i = 0; i < 100; i++) {
myqueue.enqueue(i.toString());
}
for(var j = 0; j < 100; j++) {
assert.isEqual(myqueue.dequeue(), j.toString());
}
}
if (module == require.main) {
require("os").exit(require("test").run(exports));
}
I asked for help on the Narwhal IRC channel and Kris Kowal pointed me to Chiron, a module library he's working on that already contains set and dictionary implementations. I recommend checking out the code, it highligts some of the interesting challenges of implementing collections in Javascript.
Also, this "Javascript, the Good Parts" talk by Doug Crockford (author of book of the same name) is really good:
Some time last year, I got my hands on a draft of the Ecmascript 4 (Javascript to you and me) specification, thinking that it would be interesting to implement a reference-implementation for the evolving language (or rather, a non-boring way to get credit towards my degree). I remember being puzzled by the fact that, aside from a few Microsoftees and Mozilla-people, almost all the committee participants are from Adobe. The reason, it turns out, is that Actionscript — the scripting-language used in Flash — is based on Ecmascript, and Adobe apparently wants to drive innovation for the language.
Why do I care? Earlier this month, I wrote a non-trivial Flash application (details of the project to follow in another post), and I rather liked it. The typical place to start is the Flash authoring tool, which — compared to regular IDEs like Visual Studio (VS) — seems turned on it’s head: Whereas VS is a code editor with some incidental design-modes, Flash is a graphical design-tool with an incidental code-editing feature. Trying to figure whether an object dragged on to the stage should be declared, merely initialised or just used from your code gets old pretty quickly, Javascript has enough scoping ambiguities on it’s own.
Instead, go get Flex Builder 3. It’s still in beta, but freely available and built on Eclipse with all the IDE-niceness that comes with that platform. Refactoring is a bit sketchy, and probably difficult to get right in a language like Actionscript, but otherwise it’s very well rounded. The best part is that Actionscript 3 comes with most of the good stuff from Ecmascript 4 (could someone sync the versions please?), features like packages, proper classes (as opposed to the prototype-voodoo) and optional, strong typing.
What I’m getting is that if you wanna see what’s new in Javascript, go check out Actionscript! Through Ecmascript 4 it’s becomming an interesting language, there’s a nice IDE, rich API’s, interesting code (check this out) and a community that’s more focused on building compelling apps than on perfecting their Tower of Hanoi implementations.
A client wanted signatures to be inserted automatically in emails sent from CRM. I wanted to use the built-in template-feature, but the button on the email form launches a modal dialog which makes it hard to script. I was just about to give up and do a callout, when my manager suggested eavesdropping on the dialog to see what it did to merge the template.
Wireshark revealed that no postback is involved, the dialog merely calls an undocumented web service called /AppWebServices/EmailTemplateService.asmx. The service has an operation called GetInstantiatedEmailTemplate (see pic) which returns a bunch of XML. Not wanting to deal with this myself, I poked around in the email-form source and found that Microsoft has been kind enough to provide a function that’ll insert the result of the template instantiation for you. It’s called InsertValue() and resides on the crmForm.all.description element.
While this method is obviously unsupported, I’m reliably informed that the web services in /AppWebServices are also to be found in Titan.
Here’s the code in all it’s glory, note that JavaScript SOAP Client is used for the web service call.
var template_url = 'http://foo/AppWebServices/EmailTemplateService.asmx';
var soap_js = IncludeJsByDom('http://foo/js/soapclient.js');
soap_js.attachEvent("onreadystatechange", check_load);
function Get_Text()
{
var params = new SOAPClientParameters();
params.add("templateId", '{5623E3DE-1175-DC11-A465-001B78E16CCE}');
params.add("objectId", crmForm.all.to.DataValue[0].id);
params.add("objectTypeCode", 2);
SOAPClient.invoke(
template_url,
"GetInstantiatedEmailTemplate",
params,
true,
Template_Callback);
}
function Template_Callback(vol){
xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async=false;
xmlDoc.loadXML(vol);
var body = xmlDoc.getElementsByTagName("body");
crmForm.all.description.InsertValue(body.item(0).text);
}
var check_load = function()
{
if (event.srcElement.readyState == 'loaded'
|| event.srcElement.readyState == 'complete')
{
Get_Text();
}
}