iD.presets = function() {

    // an iD.presets.Collection with methods for
    // loading new data and returning defaults

    var all = iD.presets.Collection([]),
        defaults = { area: all, line: all, point: all, vertex: all, relation: all },
        fields = {},
        universal = [],
        recent = iD.presets.Collection([]);

    // Index of presets by (geometry, tag key).
    var index = {
        point: {},
        vertex: {},
        line: {},
        area: {},
        relation: {}
    };

    all.match = function(entity, resolver) {
        var geometry = entity.geometry(resolver),
            geometryMatches = index[geometry],
            best = -1,
            match;

        for (var k in entity.tags) {
            var keyMatches = geometryMatches[k];
            if (!keyMatches) continue;

            for (var i = 0; i < keyMatches.length; i++) {
                var score = keyMatches[i].matchScore(entity);
                if (score > best) {
                    best = score;
                    match = keyMatches[i];
                }
            }
        }

        return match || all.item(geometry);
    };

    all.areaKeys = function() {

        // A closed way is considered to be an area if it has a tag with one
        // of the following keys, and the value is _not_ one of the associated
        // values for the respective key.

        var areaKeys = {};

        all.collection.forEach(function(d) {
            if (d.tags) {
                for (var key in d.tags) break;
                var value = d.tags[key];

                if (['highway', 'footway', 'railway', 'type'].indexOf(key) === -1) {
                    if (d.geometry.indexOf('area') >= 0) {
                        areaKeys[key] = areaKeys[key] || {};
                    } else if (key in areaKeys && value !== '*') {
                        areaKeys[key][value] = true;
                    }
                }
            }
        });

        return areaKeys;
    };

    all.load = function(d) {

        if (d.fields) {
            _.forEach(d.fields, function(d, id) {
                fields[id] = iD.presets.Field(id, d);
                if (d.universal) universal.push(fields[id]);
            });
        }

        if (d.presets) {
            _.forEach(d.presets, function(d, id) {
                all.collection.push(iD.presets.Preset(id, d, fields));
            });
        }

        if (d.categories) {
            _.forEach(d.categories, function(d, id) {
                all.collection.push(iD.presets.Category(id, d, all));
            });
        }

        if (d.defaults) {
            var getItem = _.bind(all.item, all);
            defaults = {
                area: iD.presets.Collection(d.defaults.area.map(getItem)),
                line: iD.presets.Collection(d.defaults.line.map(getItem)),
                point: iD.presets.Collection(d.defaults.point.map(getItem)),
                vertex: iD.presets.Collection(d.defaults.vertex.map(getItem)),
                relation: iD.presets.Collection(d.defaults.relation.map(getItem))
            };
        }

        for (var i = 0; i < all.collection.length; i++) {
            var preset = all.collection[i],
                geometry = preset.geometry;

            for (var j = 0; j < geometry.length; j++) {
                var g = index[geometry[j]];
                for (var k in preset.tags) {
                    (g[k] = g[k] || []).push(preset);
                }
            }
        }

        return all;
    };

    all.field = function(id) {
        return fields[id];
    };

    all.universal = function() {
        return universal;
    };

    all.defaults = function(geometry, n) {
        var rec = recent.matchGeometry(geometry).collection.slice(0, 4),
            def = _.uniq(rec.concat(defaults[geometry].collection)).slice(0, n - 1);
        return iD.presets.Collection(_.unique(rec.concat(def).concat(all.item(geometry))));
    };

    all.choose = function(preset) {
        if (!preset.isFallback()) {
            recent = iD.presets.Collection(_.unique([preset].concat(recent.collection)));
        }
        return all;
    };

    return all;
};

@@ -1150,49 +1150,167 @@
     
-all.areaKeys = function() %7B%0A%0A    
+// Because of the open nature of tagging, iD will never have a complete%0A    // list of tags used in OSM, so we want it to have logic like %22assume%0A
     // 
-A
+that a
  clo
@@ -1321,182 +1321,596 @@
 way 
-is considered to be an area if it has a tag with one
+with an amenity tag is an area, unless the amenity%0A    // is one of these specific types%22. This function computes a structure%0A    // that allows testing of such conditions, based on the presets designated%0A    // as as supporting (or not supporting) the area geometry.
 %0A    
+//%0A
     // 
-of the following keys, and the value is _not_ one of the associated%0A        // value
+The returned object L is a whitelist/blacklist of tags. A closed way%0A    // with a tag (k, v) is considered to be an area if %60k in L && !(v in L%5Bk%5D)%60%0A    // (see %60iD.Way#isArea()%60). In other words, the keys of L form the whitelist,%0A    // and the subkey
 s for
+m
  the 
-respective key.%0A
+blacklist.%0A    all.areaKeys = function() %7B
 %0A   
@@ -1995,20 +1995,32 @@
     
-if (
+for (var key in 
 d.tags) 
 %7B%0A  
@@ -2011,25 +2011,30 @@
  in d.tags) 
-%7B
+break;
 %0A           
@@ -2038,47 +2038,26 @@
     
-    for (var key in d.tags
+if (!key
 ) 
-b
 re
-ak;%0A    
+turn;%0A
     
@@ -2090,20 +2090,16 @@
 %5Bkey%5D;%0A%0A
-    
         
@@ -2183,28 +2183,24 @@
             
-   
- 
 if (d.geomet
@@ -2238,36 +2238,32 @@
                 
-    
 areaKeys%5Bkey%5D = 
@@ -2299,20 +2299,16 @@
         
- 
-   
 %7D else i
@@ -2358,36 +2358,32 @@
                 
-   
- 
 areaKeys%5Bkey%5D%5Bva
@@ -2399,30 +2399,8 @@
 ue;%0A
-                    %7D%0A
     

Simplify and improve documentation of areaKeys