Skip to content

v0.2.54..v0.2.55 changeset tds40.js

Garret Voltz edited this page Aug 14, 2020 · 1 revision
diff --git a/translations/tds40.js b/translations/tds40.js
index f530ab0..adb0e8e 100644
--- a/translations/tds40.js
+++ b/translations/tds40.js
@@ -34,24 +34,24 @@
 */
 
 tds40 = {
-    // getDbSchema - Load the standard schema or modify it into the TDS structure
-    getDbSchema: function() {
-        tds40.layerNameLookup = {}; // <GLOBAL> Lookup table for converting an FCODE to a layername
-        tds40.AttrLookup = {}; // <GLOBAL> Lookup table for checking what attrs are in an FCODE
+  // getDbSchema - Load the standard schema or modify it into the TDS structure
+  getDbSchema: function() {
+    tds40.layerNameLookup = {}; // <GLOBAL> Lookup table for converting an FCODE to a layername
+    tds40.AttrLookup = {}; // <GLOBAL> Lookup table for checking what attrs are in an FCODE
 
-        // Warning: This is <GLOBAL> so we can get access to it from other functions
-        tds40.rawSchema = tds40.schema.getDbSchema();
+    // Warning: This is <GLOBAL> so we can get access to it from other functions
+    tds40.rawSchema = tds40.schema.getDbSchema();
 
-        // Add the Very ESRI specific FCSubtype attribute
-        if (config.getOgrEsriFcsubtype() == 'true') tds40.rawSchema = translate.addFCSubtype(tds40.rawSchema);
+    // Add the Very ESRI specific FCSubtype attribute
+    if (config.getOgrEsriFcsubtype() == 'true') tds40.rawSchema = translate.addFCSubtype(tds40.rawSchema);
 
-        // Add the eLTDS attributes
-        if (config.getOgrTdsAddEtds() == 'true') tds40.rawSchema = translate.addEtds(tds40.rawSchema);
+    // Add the eLTDS attributes
+    if (config.getOgrTdsAddEtds() == 'true') tds40.rawSchema = translate.addEtds(tds40.rawSchema);
 
-        // Add empty "extra" feature layers if needed
-        if (config.getOgrNoteExtra() == 'file') tds40.rawSchema = translate.addExtraFeature(tds40.rawSchema);
+    // Add empty "extra" feature layers if needed
+    if (config.getOgrNoteExtra() == 'file') tds40.rawSchema = translate.addExtraFeature(tds40.rawSchema);
 
-     /*
+    /*
         // This has been removed since we no longer have text enumerations in the schema
 
         // Go go through the Schema and fix/add attributes
@@ -71,2545 +71,2586 @@ tds40 = {
         } // End For tds40.rawSchema.length
      */
 
-        // Build the TDS fcode/attrs lookup table. Note: This is <GLOBAL>
-        tds40.AttrLookup = translate.makeAttrLookup(tds40.rawSchema);
+    // Build the TDS fcode/attrs lookup table. Note: This is <GLOBAL>
+    tds40.AttrLookup = translate.makeAttrLookup(tds40.rawSchema);
 
-        // Debug
-        // print("tds40.AttrLookup");
-        // translate.dumpLookup(tds40.AttrLookup);
-
-        // Decide if we are going to use TDS structure or 1 FCODE / File
-        // if we DON't want the new structure, just return the tds40.rawSchema
-        if (config.getOgrThematicStructure() == 'false')
-        {
-            // Now build the FCODE/layername lookup table. Note: This is <GLOBAL>
-            tds40.layerNameLookup = translate.makeLayerNameLookup(tds40.rawSchema);
-
-            // Now add an o2s[A,L,P] feature to the tds40.rawSchema
-            // We can drop features but this is a nice way to see what we would drop
-            tds40.rawSchema = translate.addEmptyFeature(tds40.rawSchema);
+    // Debug
+    // print("tds40.AttrLookup");
+    // translate.dumpLookup(tds40.AttrLookup);
 
-            // Add the empty Review layers
-            tds40.rawSchema = translate.addReviewFeature(tds40.rawSchema);
-
-            // Debugging:
-            // translate.dumpSchema(tds40.rawSchema);
+    // Decide if we are going to use TDS structure or 1 FCODE / File
+    // if we DON't want the new structure, just return the tds40.rawSchema
+    if (config.getOgrThematicStructure() == 'false')
+    {
+      // Now build the FCODE/layername lookup table. Note: This is <GLOBAL>
+      tds40.layerNameLookup = translate.makeLayerNameLookup(tds40.rawSchema);
 
-            return tds40.rawSchema;
-        }
+      // Now add an o2s[A,L,P] feature to the tds40.rawSchema
+      // We can drop features but this is a nice way to see what we would drop
+      tds40.rawSchema = translate.addEmptyFeature(tds40.rawSchema);
 
-        // OK, now we build a new schema
-        var newSchema = [];
-        var layerName = '';
-        var fCode = '';
+      // Add the empty Review layers
+      tds40.rawSchema = translate.addReviewFeature(tds40.rawSchema);
 
-        // Go through the fcode/layer list, find all of the layers and build a skeleton schema
-        // layerList is used to keep track of what we have already seen
-        var layerList = [];
-        var geomType = '';
-        for (var fc in tds40.rules.thematicGroupList)
-        {
-            layerName = tds40.rules.thematicGroupList[fc];
-            if (~layerList.indexOf(layerName)) continue;  // Funky use of ~ instead of '!== -1'
-            layerList.push(layerName);
+      // Debugging:
+      // translate.dumpSchema(tds40.rawSchema);
 
-            // Now build a skeleton schema
-            if (~layerName.indexOf('Pnt'))
-            {
-                geomType = 'Point';
-            }
-            else if (~layerName.indexOf('Srf'))
-            {
-                geomType = 'Area';
-            }
-            else
-            {
-                geomType = 'Line';
-            }
+      return tds40.rawSchema;
+    }
 
-            newSchema.push({ name: layerName,
-                          desc: layerName,
-                          geom: geomType,
-                          columns:[]
-                        });
-        } // End fc loop
+    // OK, now we build a new schema
+    var newSchema = [];
+    var layerName = '';
+    var fCode = '';
 
-        // Loop through the old schema and populate the new one
-        var newSchemaLen = newSchema.length; // cached as we use this a lot
-        for (var os = 0, osLen = tds40.rawSchema.length; os < osLen; os++)
-        {
-            // The table looks like:
-            // 'PGB230':'AeronauticPnt', // AircraftHangar
-            // 'AGB230':'AeronauticSrf', // AircraftHangar
-            // 'AGB015':'AeronauticSrf', // Apron
-            // ....
-            // So we add the geometry to the FCODE
-
-            fCode = tds40.rawSchema[os].geom.charAt(0) + tds40.rawSchema[os].fcode;
-            layerName = tds40.rules.thematicGroupList[fCode];
-
-            // Loop through the new schema and find the right layer
-            for (var ns = 0; ns < newSchemaLen; ns++)
-            {
-                // If we find the layer, populate it
-                if (newSchema[ns].name == layerName)
+    // Go through the fcode/layer list, find all of the layers and build a skeleton schema
+    // layerList is used to keep track of what we have already seen
+    var layerList = [];
+    var geomType = '';
+    for (var fc in tds40.rules.thematicGroupList)
+    {
+      layerName = tds40.rules.thematicGroupList[fc];
+      if (~layerList.indexOf(layerName)) continue;  // Funky use of ~ instead of '!== -1'
+      layerList.push(layerName);
+
+      // Now build a skeleton schema
+      if (~layerName.indexOf('Pnt'))
+      {
+        geomType = 'Point';
+      }
+      else if (~layerName.indexOf('Srf'))
+      {
+        geomType = 'Area';
+      }
+      else
+      {
+        geomType = 'Line';
+      }
+
+      newSchema.push({ name: layerName,
+        desc: layerName,
+        geom: geomType,
+        columns:[]
+      });
+    } // End fc loop
+
+    // Loop through the old schema and populate the new one
+    var newSchemaLen = newSchema.length; // cached as we use this a lot
+    for (var os = 0, osLen = tds40.rawSchema.length; os < osLen; os++)
+    {
+      // The table looks like:
+      // 'PGB230':'AeronauticPnt', // AircraftHangar
+      // 'AGB230':'AeronauticSrf', // AircraftHangar
+      // 'AGB015':'AeronauticSrf', // Apron
+      // ....
+      // So we add the geometry to the FCODE
+
+      fCode = tds40.rawSchema[os].geom.charAt(0) + tds40.rawSchema[os].fcode;
+      layerName = tds40.rules.thematicGroupList[fCode];
+
+      // Loop through the new schema and find the right layer
+      for (var ns = 0; ns < newSchemaLen; ns++)
+      {
+        // If we find the layer, populate it
+        if (newSchema[ns].name == layerName)
+        {
+          // now start adding attrs from the raw schema. This Is Not Pretty
+
+          // Loop through the columns in the OLD schema
+          for (var cos = 0, cosLen = tds40.rawSchema[os].columns.length; cos < cosLen; cos++)
+          {
+            var same = false;
+            // Loop through the columns in the NEW schema
+            for (var cns = 0, cnsLen = newSchema[ns].columns.length; cns < cnsLen; cns++)
+            {
+              // If the attribute names match then we can ignore it, unless it is enumerated
+              if (tds40.rawSchema[os].columns[cos].name == newSchema[ns].columns[cns].name)
+              {
+                same = true;
+                if (tds40.rawSchema[os].columns[cos].type !== 'enumeration' ) break;
+
+                // Now for some more uglyness....
+                // loop through the enumerated values  in the OLD schema
+                for (var oen = 0, oenlen = tds40.rawSchema[os].columns[cos].enumerations.length; oen < oenlen; oen++)
                 {
-                    // now start adding attrs from the raw schema. This Is Not Pretty
-
-                    // Loop through the columns in the OLD schema
-                    for (var cos = 0, cosLen = tds40.rawSchema[os].columns.length; cos < cosLen; cos++)
+                  var esame = false;
+                  // Loop through the enumerated values in the NEW schema
+                  for (var nen = 0, nenlen = newSchema[ns].columns[cns].enumerations.length; nen < nenlen; nen++)
+                  {
+                    // If the names match, ignore it
+                    if (tds40.rawSchema[os].columns[cos].enumerations[oen].name == newSchema[ns].columns[cns].enumerations[nen].name)
                     {
-                        var same = false;
-                        // Loop through the columns in the NEW schema
-                        for (var cns = 0, cnsLen = newSchema[ns].columns.length; cns < cnsLen; cns++)
-                        {
-                            // If the attribute names match then we can ignore it, unless it is enumerated
-                            if (tds40.rawSchema[os].columns[cos].name == newSchema[ns].columns[cns].name)
-                            {
-                                same = true;
-                                if (tds40.rawSchema[os].columns[cos].type !== 'enumeration' ) break;
-
-                                // Now for some more uglyness....
-                                // loop through the enumerated values  in the OLD schema
-                                for (var oen = 0, oenlen = tds40.rawSchema[os].columns[cos].enumerations.length; oen < oenlen; oen++)
-                                {
-                                    var esame = false;
-                                    // Loop through the enumerated values in the NEW schema
-                                    for (var nen = 0, nenlen = newSchema[ns].columns[cns].enumerations.length; nen < nenlen; nen++)
-                                    {
-                                        // If the names match, ignore it
-                                        if (tds40.rawSchema[os].columns[cos].enumerations[oen].name == newSchema[ns].columns[cns].enumerations[nen].name)
-                                        {
-                                            esame = true;
-                                            break;
-                                        }
-                                    } // End nen loop
-                                    // if the enumerated value isn't in the new list, add it
-                                    if (!esame)
-                                    {
-                                        newSchema[ns].columns[cns].enumerations.push(tds40.rawSchema[os].columns[cos].enumerations[oen]);
-                                    }
-                                } // End oen loop
-                            } // End if enumeration
-                        } // End nsc loop
-
-                        // if the attr isn't in the new schema, add it
-                        if (!same)
-                        {
-                            // Remove the Default Value so we get all Null values on export
-                            // delete tds40.rawSchema[os].columns[cos].defValue;
-                            //tds40.rawSchema[os].columns[cos].defValue = undefined;
-
-                            newSchema[ns].columns.push(tds40.rawSchema[os].columns[cos]);
-                        }
-                    } // End osc loop
-                } // End if layerName
-            } // End newSchema loop
-        } // end tds40.rawSchema loop
-
-        // Create a lookup table of TDS structures attributes. Note this is <GLOBAL>
-        tdsAttrLookup = translate.makeTdsAttrLookup(newSchema);
-
-        // Debug:
-        // print("tdsAttrLookup");
-        // translate.dumpLookup(tdsAttrLookup);
-
-        // Add the ESRI Feature Dataset name to the schema
-        // newSchema = translate.addFdName(newSchema,'TDS');
-        if (config.getOgrEsriFdname() !== "") newSchema = translate.addFdName(newSchema,config.getOgrEsriFdname());
+                      esame = true;
+                      break;
+                    }
+                  } // End nen loop
+                  // if the enumerated value isn't in the new list, add it
+                  if (!esame)
+                  {
+                    newSchema[ns].columns[cns].enumerations.push(tds40.rawSchema[os].columns[cos].enumerations[oen]);
+                  }
+                } // End oen loop
+              } // End if enumeration
+            } // End nsc loop
+
+            // if the attr isn't in the new schema, add it
+            if (!same)
+            {
+              // Remove the Default Value so we get all Null values on export
+              // delete tds40.rawSchema[os].columns[cos].defValue;
+              //tds40.rawSchema[os].columns[cos].defValue = undefined;
+
+              newSchema[ns].columns.push(tds40.rawSchema[os].columns[cos]);
+            }
+          } // End osc loop
+        } // End if layerName
+      } // End newSchema loop
+    } // end tds40.rawSchema loop
 
-        // Now add the o2s feature to the tds40.rawSchema
-        // We can drop features but this is a nice way to see what we would drop
-        // NOTE: We add these feature AFTER adding the ESRI Feature Dataset so that they
-        // DON'T get put under the Feature Dataset in the output
-        newSchema = translate.addEmptyFeature(newSchema);
+    // Create a lookup table of TDS structures attributes. Note this is <GLOBAL>
+    tdsAttrLookup = translate.makeTdsAttrLookup(newSchema);
 
-        // Add the empty Review layers
-        newSchema = translate.addReviewFeature(newSchema);
+    // Debug:
+    // print("tdsAttrLookup");
+    // translate.dumpLookup(tdsAttrLookup);
 
-        // Debug:
-        // translate.dumpSchema(newSchema);
+    // Add the ESRI Feature Dataset name to the schema
+    // newSchema = translate.addFdName(newSchema,'TDS');
+    if (config.getOgrEsriFdname() !== '') newSchema = translate.addFdName(newSchema,config.getOgrEsriFdname());
 
-        return newSchema;
+    // Now add the o2s feature to the tds40.rawSchema
+    // We can drop features but this is a nice way to see what we would drop
+    // NOTE: We add these feature AFTER adding the ESRI Feature Dataset so that they
+    // DON'T get put under the Feature Dataset in the output
+    newSchema = translate.addEmptyFeature(newSchema);
 
-    }, // End getDbSchema
+    // Add the empty Review layers
+    newSchema = translate.addReviewFeature(newSchema);
 
-    // validateAttrs: Clean up the supplied attr list by dropping anything that should not be part of the
-    //                feature, checking enumerated values and populating the OTH field
-    validateAttrs: function(geometryType,attrs) {
+    // Debug:
+    // translate.dumpSchema(newSchema);
 
-        // First, use the lookup table to quickly drop all attributes that are not part of the feature
-        // This is quicker than going through the Schema due to the way the Schema is arranged
-        var attrList = tds40.AttrLookup[geometryType.toString().charAt(0) + attrs.F_CODE];
+    return newSchema;
 
-        var othList = {};
+  }, // End getDbSchema
 
-        if (attrs.OTH)
-        {
-            othList = translate.parseOTH(attrs.OTH); // Unpack the OTH field
-            delete attrs.OTH;
-        }
+  // validateAttrs: Clean up the supplied attr list by dropping anything that should not be part of the
+  //                feature, checking enumerated values and populating the OTH field
+  validateAttrs: function(geometryType,attrs, notUsed, transMap) {
 
-        if (attrList != undefined)
-        {
-           for (var val in attrs)
-           {
-                if (attrList.indexOf(val) == -1)
-                {
-                    if (val in othList)
-                    {
-                        //Debug:
-                        // print('Validate: Dropping OTH: ' + val + '  (' + othList[val] + ')');
-                        delete othList[val];
-                    }
+    // First, use the lookup table to quickly drop all attributes that are not part of the feature
+    // This is quicker than going through the Schema due to the way the Schema is arranged
+    var attrList = tds40.AttrLookup[geometryType.toString().charAt(0) + attrs.F_CODE];
 
-                    hoot.logDebug('Validate: Dropping ' + val + '  from ' + attrs.F_CODE);
-                    delete attrs[val];
+    var othList = {};
 
-                    // Since we deleted the attribute, Skip the text check
-                    continue;
-                }
+    if (attrs.OTH)
+    {
+      othList = translate.parseOTH(attrs.OTH); // Unpack the OTH field
+      delete attrs.OTH;
+    }
 
-                // Now check the length of the text fields
-                // We need more info from the customer about this: What to do if it is too long
-                if (val in tds40.rules.txtLength)
-                {
-                    if (attrs[val].length > tds40.rules.txtLength[val])
-                    {
-                        // First try splitting the attribute and grabbing the first value
-                        var tStr = attrs[val].split(';');
-                        if (tStr[0].length <= tds40.rules.txtLength[val])
-                        {
-                            attrs[val] = tStr[0];
-                        }
-                        else
-                        {
-                            hoot.logDebug('Validate: Attribute ' + val + ' is ' + attrs[val].length + ' characters long. Truncating to ' + tds40.rules.txtLength[val] + ' characters.');
-                            // Still too long. Chop to the maximum length
-                            attrs[val] = tStr[0].substring(0,tds40.rules.txtLength[val]);
-                        }
-                    } // End text attr length > max length
-                    continue;
-                } // End in txtLength
-            } // End attrs loop
-        }
-        else
+    if (attrList != undefined)
+    {
+      for (var val in attrs)
+      {
+        if (attrList.indexOf(val) == -1)
         {
-            hoot.logDebug('Validate: No attrList for ' + attrs.F_CODE + ' ' + geometryType);
-        } // End Drop attrs
+          if (val in othList)
+          {
+            //Debug:
+            // print('Validate: Dropping OTH: ' + val + '  (' + othList[val] + ')');
+            delete othList[val];
+          }
 
-        // Repack the OTH field
-        if (Object.keys(othList).length > 0)
-        {
-            attrs.OTH = translate.packOTH(othList);
+          if (val in transMap)
+          {
+            notUsed[transMap[val][1]] = transMap[val][2];
             // Debug:
-            // print('New OTH: ' + attrs.OTH);
-        }
+            // print('Validate: Re-Adding ' + transMap[val][1] + ' = ' + transMap[val][2] + ' to notUsed');
+          }
 
-        // No quick and easy way to do this unless we build yet another lookup table
-        var feature = {};
+          hoot.logDebug('Validate: Dropping ' + val + ' = ' + attrs[val] + ' from ' + attrs.F_CODE);
+          delete attrs[val];
+
+          // Since we deleted the attribute, Skip the text check
+          continue;
+        }
 
-        for (var i=0, sLen = tds40.rawSchema.length; i < sLen; i++)
+        // Now check the length of the text fields
+        // We need more info from the customer about this: What to do if it is too long
+        if (val in tds40.rules.txtLength)
         {
-            if (tds40.rawSchema[i].fcode == attrs.F_CODE && tds40.rawSchema[i].geom == geometryType)
+          if (attrs[val].length > tds40.rules.txtLength[val])
+          {
+            // First try splitting the attribute and grabbing the first value
+            var tStr = attrs[val].split(';');
+            if (tStr[0].length <= tds40.rules.txtLength[val])
             {
-                feature = tds40.rawSchema[i];
-                break;
+              attrs[val] = tStr[0];
             }
-        }
+            else
+            {
+              hoot.logDebug('Validate: Attribute ' + val + ' is ' + attrs[val].length + ' characters long. Truncating to ' + tds40.rules.txtLength[val] + ' characters.');
+              // Still too long. Chop to the maximum length
+              attrs[val] = tStr[0].substring(0,tds40.rules.txtLength[val]);
+            }
+          } // End text attr length > max length
+          continue;
+        } // End in txtLength
+      } // End attrs loop
+    }
+    else
+    {
+      hoot.logDebug('Validate: No attrList for ' + attrs.F_CODE + ' ' + geometryType);
+    } // End Drop attrs
 
-        // Now validate the Enumerated values
-        for (var i=0, cLen = feature['columns'].length; i < cLen; i++)
-        {
-            // Skip non enumeratied attributes
-            if (feature.columns[i].type !== 'enumeration') continue;
+    // Repack the OTH field
+    if (Object.keys(othList).length > 0)
+    {
+      attrs.OTH = translate.packOTH(othList);
+      // Debug:
+      // print('New OTH: ' + attrs.OTH);
+    }
 
-            var enumName = feature.columns[i].name;
+    // No quick and easy way to do this unless we build yet another lookup table
+    var feature = {};
 
-            // Skip stuff that is missing and will end up as a default value
-            if (! attrs[enumName]) continue;
+    for (var i=0, sLen = tds40.rawSchema.length; i < sLen; i++)
+    {
+      if (tds40.rawSchema[i].fcode == attrs.F_CODE && tds40.rawSchema[i].geom == geometryType)
+      {
+        feature = tds40.rawSchema[i];
+        break;
+      }
+    }
+
+    // Now validate the Enumerated values
+    for (var i=0, cLen = feature['columns'].length; i < cLen; i++)
+    {
+      // Skip non enumeratied attributes
+      if (feature.columns[i].type !== 'enumeration') continue;
 
-            var attrValue = attrs[enumName];
-            var enumList = feature.columns[i].enumerations;
-            var enumValueList = [];
+      var enumName = feature.columns[i].name;
 
-            // Pull all of the values out of the enumerated list to make life easier
-            for (var j=0, elen = enumList.length; j < elen; j++) enumValueList.push(enumList[j].value);
+      // Skip stuff that is missing and will end up as a default value
+      if (! attrs[enumName]) continue;
 
-            // If we DONT have the value in the list, add it to the OTH or MEMO field
-            if (enumValueList.indexOf(attrValue) == -1)
-            {
-                var othVal = '(' + enumName + ':' + attrValue + ')';
+      var attrValue = attrs[enumName];
+      var enumList = feature.columns[i].enumerations;
+      var enumValueList = [];
 
-                // No "Other" value. Push to the Memo field
-                if (enumValueList.indexOf('999') == -1)
-                {
-                    // Set the offending enumerated value to the default value
-                    attrs[enumName] = feature.columns[i].defValue;
+      // Pull all of the values out of the enumerated list to make life easier
+      for (var j=0, elen = enumList.length; j < elen; j++) enumValueList.push(enumList[j].value);
 
-                    hoot.logTrace('Validate: Enumerated Value: ' + attrValue + ' not found in ' + enumName + ' Setting ' + enumName + ' to its default value (' + feature.columns[i].defValue + ')');
+      // If we DONT have the value in the list, add it to the OTH or MEMO field
+      if (enumValueList.indexOf(attrValue) == -1)
+      {
+        var othVal = '(' + enumName + ':' + attrValue + ')';
 
-                    attrs.ZI006_MEM = translate.appendValue(attrs.ZI006_MEM,othVal,';');
-                }
-                else
-                {
-                    // Set the offending enumerated value to the "other" value
-                    attrs[enumName] = '999';
+        // No "Other" value. Push to the Memo field
+        if (enumValueList.indexOf('999') == -1)
+        {
+          // Set the offending enumerated value to the default value
+          attrs[enumName] = feature.columns[i].defValue;
 
-                    hoot.logTrace('Validate: Enumerated Value: ' + attrValue + ' not found in ' + enumName + ' Setting OTH and ' + enumName + ' to Other (999)');
+          hoot.logTrace('Validate: Enumerated Value: ' + attrValue + ' not found in ' + enumName + ' Setting ' + enumName + ' to its default value (' + feature.columns[i].defValue + ')');
 
-                    attrs.OTH = translate.appendValue(attrs.OTH,othVal,' ');
-                }
+          attrs.ZI006_MEM = translate.appendValue(attrs.ZI006_MEM,othVal,';');
+        }
+        else
+        {
+          // Set the offending enumerated value to the "other" value
+          attrs[enumName] = '999';
 
-            } // End attrValue in enumList
+          hoot.logTrace('Validate: Enumerated Value: ' + attrValue + ' not found in ' + enumName + ' Setting OTH and ' + enumName + ' to Other (999)');
 
-        } // End Validate Enumerations
+          attrs.OTH = translate.appendValue(attrs.OTH,othVal,' ');
+        }
 
-    }, // End validateAttrs
+      } // End attrValue in enumList
 
+    } // End Validate Enumerations
 
-    // validateTDSAttrs - Clean up the TDS format attrs.  This sets all of the extra attrs to be "undefined"
-    validateTDSAttrs: function(gFcode, attrs) {
+  }, // End validateAttrs
 
-        var tdsAttrList = tdsAttrLookup[tds40.rules.thematicGroupList[gFcode]];
-        var AttrList = tds40.AttrLookup[gFcode];
 
-        for (var i = 0, len = tdsAttrList.length; i < len; i++)
-        {
-            if (AttrList.indexOf(tdsAttrList[i]) == -1) attrs[tdsAttrList[i]] = undefined;
-        }
-    }, // End validateTDSAttrs
+  // validateTDSAttrs - Clean up the TDS format attrs.  This sets all of the extra attrs to be "undefined"
+  validateTDSAttrs: function(gFcode, attrs) {
 
+    var tdsAttrList = tdsAttrLookup[tds40.rules.thematicGroupList[gFcode]];
+    var AttrList = tds40.AttrLookup[gFcode];
 
-    // Sort out if we need to return more than one feature
-    // This is generally for Roads, Railways, bridges, tunnels etc
-    manyFeatures: function(geometryType, tags, attrs)
+    for (var i = 0, len = tdsAttrList.length; i < len; i++)
     {
-        // Add the first feature to the structure that we return
-        var returnData = [{attrs:attrs, tableName:''}];
+      if (AttrList.indexOf(tdsAttrList[i]) == -1) attrs[tdsAttrList[i]] = undefined;
+    }
+  }, // End validateTDSAttrs
 
-        // Quit early if we don't need to check anything. We are only looking at linework
-        if (geometryType !== 'Line') return returnData;
 
-        // Only looking at roads & railways with something else tacked on
-        if (!(tags.highway || tags.railway)) return returnData;
+  // Sort out if we need to return more than one feature
+  // This is generally for Roads, Railways, bridges, tunnels etc
+  manyFeatures: function(geometryType, tags, attrs,transMap)
+  {
+    // Add the first feature to the structure that we return
+    var returnData = [{attrs:attrs, tableName:''}];
 
-        // Check the list of secondary/tertiary etc features
-        if (!(tags.bridge || tags.tunnel || tags.embankment || tags.cutting || tags.ford)) return returnData;
+    // Quit early if we don't need to check anything. We are only looking at linework
+    if (geometryType !== 'Line') return returnData;
 
-        // We are going to make another feature so copy tags and trash the UUID so it gets a new one
-        var newFeatures = [];
-        var newAttributes = {};
-        var nTags = JSON.parse(JSON.stringify(tags));
-        delete nTags.uuid;
-        delete nTags['hoot:id'];
+    // Only looking at roads & railways with something else tacked on
+    if (!(tags.highway || tags.railway)) return returnData;
 
-        // Now drop the tags that made the FCODE
-        switch(attrs.F_CODE)
-        {
-            case 'AN010': // Railway
-            case 'AN050': // Railway Sidetrack
-                delete nTags.railway;
-                newAttributes.TRS = '12'; // Transport Type = Railway
-                break;
-
-            case 'AP010': // Cart Track
-            case 'AP030': // Road
-            case 'AP050': // Trail
-                switch (nTags.highway)
-                {
-                    case 'pedestrian':
-                    case 'footway':
-                    case 'steps':
-                    case 'path':
-                    case 'bridleway':
-                    case 'cycleway':
-                        newAttributes.TRS = '9'; // Transport Type = Pedestrian
-                        break;
-
-                    default:
-                        newAttributes.TRS = '13'; // Transport Type = Road
-                }
-                delete nTags.highway;
-                break;
+    // Check the list of secondary/tertiary etc features
+    if (!(tags.bridge || tags.tunnel || tags.embankment || tags.cutting || tags.ford)) return returnData;
 
-            case 'AQ040': // Bridge
-                delete nTags.bridge;
-                newAttributes.SBB = '1001'; // Supported By Bridge Span = True
-                break;
+    // We are going to make another feature so copy tags and trash the UUID so it gets a new one
+    var newFeatures = [];
+    var newAttributes = {};
+    var nTags = JSON.parse(JSON.stringify(tags));
+    delete nTags.uuid;
+    delete nTags['hoot:id'];
 
-            case 'AQ130': // Tunnel
-                delete nTags.tunnel;
-                newAttributes.CWT = '1001'; // Contained Within Tunnel = True
-                break;
+    // Now drop the tags that made the FCODE
+    switch(attrs.F_CODE)
+    {
+    case 'AN010': // Railway
+    case 'AN050': // Railway Sidetrack
+      delete nTags.railway;
+      newAttributes.TRS = '12'; // Transport Type = Railway
+      break;
+
+    case 'AP010': // Cart Track
+    case 'AP030': // Road
+    case 'AP050': // Trail
+      switch (nTags.highway)
+      {
+      case 'pedestrian':
+      case 'footway':
+      case 'steps':
+      case 'path':
+      case 'bridleway':
+      case 'cycleway':
+        newAttributes.TRS = '9'; // Transport Type = Pedestrian
+        break;
+
+      default:
+        newAttributes.TRS = '13'; // Transport Type = Road
+      }
+      delete nTags.highway;
+      break;
+
+    case 'AQ040': // Bridge
+      delete nTags.bridge;
+      newAttributes.SBB = '1001'; // Supported By Bridge Span = True
+      break;
+
+    case 'AQ130': // Tunnel
+      delete nTags.tunnel;
+      newAttributes.CWT = '1001'; // Contained Within Tunnel = True
+      break;
+
+    case 'BH070': // Ford
+      delete nTags.ford;
+      break;
+
+    case 'DB070': // Cutting
+      delete nTags.cutting;
+      break;
+
+    case 'DB090': // Embankment
+      delete nTags.embankment;
+      break;
+
+    default:
+      // Debug
+      hoot.logWarn('ManyFeatures: Should get to here');
+    } // end switch
+
+    // Now make new features based on what tags are left
+    if (nTags.railway)
+    {
+      newFeatures.push({attrs: JSON.parse(JSON.stringify(newAttributes)), tags: JSON.parse(JSON.stringify(nTags))});
+      delete nTags.railway;
+    }
 
-            case 'BH070': // Ford
-                delete nTags.ford;
-                break;
+    if (nTags.highway)
+    {
+      if (nTags.highway == 'track') newAttributes.TRS = '3'; // Cart Track TRS = Automotive
+      newFeatures.push({attrs: JSON.parse(JSON.stringify(newAttributes)), tags: JSON.parse(JSON.stringify(nTags))});
+      delete nTags.highway;
+    }
 
-            case 'DB070': // Cutting
-                delete nTags.cutting;
-                break;
+    if (nTags.cutting)
+    {
+      newAttributes.F_CODE = 'DB070';
+      newFeatures.push({attrs: JSON.parse(JSON.stringify(newAttributes)), tags: JSON.parse(JSON.stringify(nTags))});
+      delete nTags.cutting;
+    }
 
-            case 'DB090': // Embankment
-                delete nTags.embankment;
-                break;
+    if (nTags.bridge && nTags.bridge !== 'no')
+    {
+      newAttributes.F_CODE = 'AQ040';
+      newFeatures.push({attrs: JSON.parse(JSON.stringify(newAttributes)), tags: JSON.parse(JSON.stringify(nTags))});
+      delete nTags.bridge;
+    }
 
-            default:
-                // Debug
-                hoot.logWarn('ManyFeatures: Should get to here');
-        } // end switch
+    if (nTags.tunnel && nTags.tunnel !== 'no')
+    {
+      newAttributes.F_CODE = 'AQ130';
+      newFeatures.push({attrs: JSON.parse(JSON.stringify(newAttributes)), tags: JSON.parse(JSON.stringify(nTags))});
+      delete nTags.tunnel;
+    }
 
-        // Now make new features based on what tags are left
-        if (nTags.railway)
-        {
-            newFeatures.push({attrs: JSON.parse(JSON.stringify(newAttributes)), tags: JSON.parse(JSON.stringify(nTags))});
-            delete nTags.railway;
-        }
+    if (nTags.ford && nTags.ford !== 'no')
+    {
+      newAttributes.F_CODE = 'BH070';
+      newFeatures.push({attrs: JSON.parse(JSON.stringify(newAttributes)), tags: JSON.parse(JSON.stringify(nTags))});
+      delete nTags.ford;
+    }
 
-        if (nTags.highway)
-        {
-            if (nTags.highway == 'track') newAttributes.TRS = '3'; // Cart Track TRS = Automotive
-            newFeatures.push({attrs: JSON.parse(JSON.stringify(newAttributes)), tags: JSON.parse(JSON.stringify(nTags))});
-            delete nTags.highway;
-        }
+    if (nTags.embankment)
+    {
+      newAttributes.F_CODE = 'DB090';
+      newFeatures.push({attrs: JSON.parse(JSON.stringify(newAttributes)), tags: JSON.parse(JSON.stringify(nTags))});
+      delete nTags.embankment;
+    }
 
-        if (nTags.cutting)
-        {
-            newAttributes.F_CODE = 'DB070';
-            newFeatures.push({attrs: JSON.parse(JSON.stringify(newAttributes)), tags: JSON.parse(JSON.stringify(nTags))});
-            delete nTags.cutting;
-        }
+    // Loop through the new features and process them
+    for (var i = 0, nFeat = newFeatures.length; i < nFeat; i++)
+    {
+      // pre processing
+      tds40.applyToTdsPreProcessing(newFeatures[i]['tags'], newFeatures[i]['attrs'], geometryType);
 
-        if (nTags.bridge && nTags.bridge !== 'no')
-        {
-            newAttributes.F_CODE = 'AQ040';
-            newFeatures.push({attrs: JSON.parse(JSON.stringify(newAttributes)), tags: JSON.parse(JSON.stringify(nTags))});
-            delete nTags.bridge;
-        }
+      // apply the simple number and text biased rules
+      // Note: These are BACKWARD, not forward!
+      translate.numToOgr(newFeatures[i]['attrs'], newFeatures[i]['tags'], tds40.rules.numBiased,tds40.rules.intList,transMap);
+      translate.txtToOgr(newFeatures[i]['attrs'], newFeatures[i]['tags'], tds40.rules.txtBiased,transMap);
 
-        if (nTags.tunnel && nTags.tunnel !== 'no')
-        {
-            newAttributes.F_CODE = 'AQ130';
-            newFeatures.push({attrs: JSON.parse(JSON.stringify(newAttributes)), tags: JSON.parse(JSON.stringify(nTags))});
-            delete nTags.tunnel;
-        }
+      // one 2 one - we call the version that knows about OTH fields
+      translate.applyTdsOne2One(newFeatures[i]['tags'], newFeatures[i]['attrs'], tds40.lookup, tds40.fcodeLookup,transMap);
 
-        if (nTags.ford && nTags.ford !== 'no')
-        {
-            newAttributes.F_CODE = 'BH070';
-            newFeatures.push({attrs: JSON.parse(JSON.stringify(newAttributes)), tags: JSON.parse(JSON.stringify(nTags))});
-            delete nTags.ford;
-        }
+      // post processing
+      tds40.applyToTdsPostProcessing(newFeatures[i]['tags'], newFeatures[i]['attrs'], geometryType, {});
 
-        if (nTags.embankment)
-        {
-            newAttributes.F_CODE = 'DB090';
-            newFeatures.push({attrs: JSON.parse(JSON.stringify(newAttributes)), tags: JSON.parse(JSON.stringify(nTags))});
-            delete nTags.embankment;
-        }
+      returnData.push({attrs: newFeatures[i]['attrs'],tableName: ''});
+    }
 
-        // Loop through the new features and process them
-        for (var i = 0, nFeat = newFeatures.length; i < nFeat; i++)
-        {
-            // pre processing
-            tds40.applyToTdsPreProcessing(newFeatures[i]['tags'], newFeatures[i]['attrs'], geometryType);
+    return returnData;
+  }, // End manyFeatures
 
-            // apply the simple number and text biased rules
-            // Note: These are BACKWARD, not forward!
-            translate.applySimpleNumBiased(newFeatures[i]['attrs'], newFeatures[i]['tags'], tds40.rules.numBiased,'backward',tds40.rules.intList);
-            translate.applySimpleTxtBiased(newFeatures[i]['attrs'], newFeatures[i]['tags'], tds40.rules.txtBiased,'backward');
+  // Doesn't do much but saves typing the same code out a few times in the to TDS Pre Processing
+  // NOTE if these are points, we drop the railway/highway tags since we can't make transport features out of these
+  fixTransType : function(tags,geometry)
+  {
+    if (tags.railway)
+    {
+      tags['transport:type'] = 'railway';
+      if (geometry == 'Point') delete tags.railway;
+    }
+    else if (tags.highway && ['path','pedestrian','steps','trail'].indexOf(tags.highway) > -1)
+    {
+      tags['transport:type'] = 'pedestrian';
+      if (geometry == 'Point') delete tags.highway;
+    }
+    else if (tags.highway)
+    {
+      tags['transport:type'] = 'road';
+      if (geometry == 'Point') delete tags.highway;
+    }
+  },
+
+  // Untangle TDS attributes & OSM tags
+  // Some people have been editing OSM files and inserting TDS attributes
+  untangleAttributes: function (attrs, tags)
+  {
+    // If we use ogr2osm, the GDAL driver jams any tag it doesn't know about into an "other_tags" tag
+    // We need to unpack this before we can do anything
+    if (attrs.other_tags)
+    {
+      var tList = attrs.other_tags.split('","');
 
-            // one 2 one - we call the version that knows about OTH fields
-            translate.applyTdsOne2One(newFeatures[i]['tags'], newFeatures[i]['attrs'], tds40.lookup, tds40.fcodeLookup);
+      delete attrs.other_tags;
 
-            // post processing
-            tds40.applyToTdsPostProcessing(newFeatures[i]['tags'], newFeatures[i]['attrs'], geometryType, {});
+      for (var val in tList)
+      {
+        vList = tList[val].split('"=>"');
 
-            returnData.push({attrs: newFeatures[i]['attrs'],tableName: ''});
-        }
+        attrs[vList[0].replace('"','')] = vList[1].replace('"','');
 
-        return returnData;
-    }, // End manyFeatures
+        // Debug
+        //print('val: ' + tList[val] + '  vList[0] = ' + vList[0] + '  vList[1] = ' + vList[1]);
+      }
+    }
 
-    // Doesn't do much but saves typing the same code out a few times in the to TDS Pre Processing
-    // NOTE if these are points, we drop the railway/highway tags since we can't make transport features out of these
-    fixTransType : function(tags,geometry)
+    for (var col in attrs)
     {
-        if (tags.railway)
-        {
-            tags['transport:type'] = 'railway';
-            if (geometry == 'Point') delete tags.railway;
-        }
-        else if (tags.highway && ['path','pedestrian','steps','trail'].indexOf(tags.highway) > -1)
+      // Sort out FCODE funkyness:  f_CODE, F_Code etc
+      var tKey = col.toLowerCase();
+      tKey = tKey.replace(/\s/g, '').replace(/_/g, '');
+
+      if (tKey == 'fcode' && col !== 'F_CODE')
+      {
+        attrs.F_CODE = attrs[col];
+        delete attrs[col];
+        continue;
+      }
+
+      // Check for an FCODE as a tag
+      if (col in tds40.fcodeLookup['F_CODE'])
+      {
+        attrs.F_CODE = col;
+        delete attrs[col];
+        continue;
+      }
+
+      // Stuff to be ignored or that gets swapped later - See applyToOsmPreProcessing
+      if (~tds40.rules.ignoreList.indexOf(col)) continue;
+
+      // Look for Attributes
+      if (col in tds40.rules.numBiased) continue;
+
+      if (col in tds40.rules.txtBiased) continue;
+
+      if (col in tds40.lookup) continue;
+
+      // Drop the "GEOM" attribute
+      if (col == 'GEOM')
+      {
+        delete attrs[col];
+        continue;
+      }
+
+      // Not an Attribute so push it to the tags object
+      tags[col] = attrs[col];
+      delete attrs[col];
+    }
+
+  }, // End untangleAttributes
+
+
+  // #####################################################################################################
+  // ##### Start of the xxToOsmxx Block #####
+  applyToOsmPreProcessing: function(attrs, layerName, geometryType)
+  {
+    // Drop the FCSUBTYPE since we don't use it
+    if (attrs.FCSUBTYPE) delete attrs.FCSUBTYPE;
+
+    // List of data values to drop/ignore
+    var ignoreList = { '-999999.0':1,'-999999':1,'noinformation':1 };
+
+    // List of attributes that can't have '0' as a value
+    var noZeroList = ['BNF','DZC','LC1','LC2','LC3','LC4','LTN','NOS','NPL','VST','WD1','WD2','WT2','ZI016_WD1'];
+
+    // This is a handy loop. We use it to:
+    // 1) Remove all of the "No Information" and -999999 fields
+    // 2) Convert all of the Attrs to uppercase - if needed
+    // 3) Swap some of the funky named attrs around
+    for (var col in attrs)
+    {
+      // slightly ugly but we would like to account for: 'No Information','noInformation' etc
+      // First, push to lowercase
+      var attrValue = attrs[col].toString().toLowerCase();
+
+      // Get rid of the spaces in the text
+      attrValue = attrValue.replace(/\s/g, '');
+
+      // Wipe out the useless values
+      if (attrs[col] == '' || attrValue in ignoreList || attrs[col] in ignoreList)
+      {
+        delete attrs[col]; // debug: Comment this out to leave all of the No Info stuff in for testing
+        continue;
+      }
+
+      // Remove attributes with '0' values if they can't be '0'
+      if (noZeroList.indexOf(col) > -1 && attrs[col] == '0')
+      {
+        delete attrs[col];
+        continue;
+      }
+
+      // Push the attribute to upper case - if needed
+      var c = col.toUpperCase();
+      if (c !== col)
+      {
+        attrs[c] = attrs[col];
+        delete attrs[col];
+        col = c; // For the rest of this loop iteration
+      }
+
+      // Now see if we need to swap attr names
+      if (col in tds40.rules.swapListIn)
+      {
+        // Debug:
+        // print('Swapped: ' + tds40.rules.swapList[i]);
+        attrs[tds40.rules.swapListIn[col]] = attrs[col];
+        delete attrs[col];
+        continue;
+      }
+
+      // The following is to account for TDSv30 vs TDSv40 attribute naming. Somehow
+      // they had the bright idea to rename XXX1 to XXX for a stack of features:
+      // E.g. FFN1 -> FFN
+      var endChar = col.charAt(col.length - 1);
+      if (endChar == 1 && ['LC1','ZI016_WD1','ZI020_FI1','MGL1'].indexOf(col) == -1)
+      {
+        attrs[col.slice(0,-1)] = attrs[col];
+        // Debug:
+        // print('Swapped: ' + col);
+        delete attrs[col];
+        continue;
+      }
+    } // End in attrs loop
+
+    // Drop all of the XXX Closure default values IFF the associated attributes are not set
+    // Doing this after the main cleaning loop so all of the -999999 values are
+    // already gone and we can just check for existance
+    for (var i in tds40.rules.closureList)
+    {
+      if (attrs[i])
+      {
+        if (attrs[tds40.rules.closureList[i][0]] || attrs[tds40.rules.closureList[i][1]])
         {
-            tags['transport:type'] = 'pedestrian';
-            if (geometry == 'Point') delete tags.highway;
+          continue;
         }
-        else if (tags.highway)
+        else
         {
-            tags['transport:type'] = 'road';
-            if (geometry == 'Point') delete tags.highway;
+          delete attrs[i];
         }
-    },
+      }
+    } // End closureList
 
-    // Untangle TDS attributes & OSM tags
-    // Some people have been editing OSM files and inserting TDS attributes
-    untangleAttributes: function (attrs, tags)
+    // Now find an F_CODE
+    if (attrs.F_CODE)
     {
-        // If we use ogr2osm, the GDAL driver jams any tag it doesn't know about into an "other_tags" tag
-        // We need to unpack this before we can do anything
-        if (attrs.other_tags)
+      // Drop the the "Not Found" F_CODE. This is from the UI
+      if (attrs.F_CODE == 'Not found') delete attrs.F_CODE;
+    }
+    else if (attrs.FCODE)
+    {
+      attrs.F_CODE = attrs.FCODE;
+      delete attrs.FCODE;
+    }
+    else
+    {
+      // Time to find an FCODE based on the filename
+      // Funky but it makes life easier
+      var llayerName = layerName.toString().toLowerCase();
+
+      for (var row in tds40.rules.fCodeMap)
+      {
+        for (var val in tds40.rules.fCodeMap[row][1])
         {
-            var tList = attrs.other_tags.split('","');
+          if (llayerName == tds40.rules.fCodeMap[row][1][val])
+          {
+            attrs.F_CODE = tds40.rules.fCodeMap[row][0];
+            break;
+          }
+        }
+      }
+    } // End of Find an FCode
 
-            delete attrs.other_tags;
+  }, // End of applyToOsmPreProcessing
 
-            for (var val in tList)
-            {
-                vList = tList[val].split('"=>"');
 
-                attrs[vList[0].replace('"','')] = vList[1].replace('"','');
+  // #####################################################################################################
+  applyToOsmPostProcessing : function (attrs, tags, layerName, geometryType)
+  {
+    // Unpack the ZI006_MEM field
+    if (tags.note)
+    {
+      var tObj = translate.unpackMemo(tags.note);
+
+      if (tObj.tags !== '')
+      {
+        var tTags = JSON.parse(tObj.tags);
+        for (i in tTags)
+        {
+          // Debug
+          // print('Memo: Add: ' + i + ' = ' + tTags[i]);
+          if (tags[tTags[i]]) hoot.logWarn('Unpacking ZI006_MEM, overwriting ' + i + ' = ' + tags[i] + '  with ' + tTags[i]);
+          tags[i] = tTags[i];
+        }
+      }
+
+      if (tObj.text && tObj.text !== '')
+      {
+        tags.note = tObj.text;
+      }
+      else
+      {
+        delete tags.note;
+      }
+    } // End unpack tags.note
+
+    // Add the LayerName to the source
+    if ((! tags.source) && layerName !== '') tags.source = 'tdsv40:' + layerName.toLowerCase();
+
+    // If we have a UFI, store it. Some of the MAAX data has a LINK_ID instead of a UFI
+    if (tags.uuid)
+    {
+      tags.uuid = '{' + tags['uuid'].toString().toLowerCase() + '}';
+    }
+    else
+    {
+      if (tds40.configIn.OgrAddUuid == 'true') tags.uuid = createUuid();
+    }
 
-                // Debug
-                //print('val: ' + tList[val] + '  vList[0] = ' + vList[0] + '  vList[1] = ' + vList[1]);
-            }
-        }
+    if (tds40.osmPostRules == undefined)
+    {
+      // ##############
+      // A "new" way of specifying rules. Jason came up with this while playing around with NodeJs
+      //
+      // Rules format:  ["test expression","output result"];
+      // Note: t = tags, a = attrs and attrs can only be on the RHS
+      var rulesList = [
+        ['t.barrier == \'dragons_teeth\' && !(t.tank_trap)','t.barrier = \'tank_trap\'; t.tank_trap = \'dragons_teeth\''],
+        ['t.amenity == \'stop\' && t[\'transport:type\'] == \'bus\'','t.highway = \'bus_stop\';'],
+        ['t.boundary == \'protected_area\' && !(t.protect_class)','t.protect_class = \'4\';'],
+        ['t[\'bridge:movable\'] && t[\'bridge:movable\'] !== \'no\' && t[\'bridge:movable\'] !== \'unknown\'','t.bridge = \'movable\''],
+        ['t.cable ==\'yes\' && t[\'cable:type\'] == \'power\'',' t.power = \'line\'; delete t.cable; delete t[\'cable:type\']'],
+        ['t.control_tower == \'yes\' && t.use == \'air_traffic_control\'','t[\'tower:type\'] = \'observation\''],
+        ['t.desert_surface','t.surface = t.desert_surface; delete t.desert_surface'],
+        ['t.diplomatic && !(t.amenity)','t.amenity = \'embassy\''],
+        ['t[\'generator:source\'] == \'wind\'','t.power = \'generator\''],
+        ['t.historic == \'castle\' && !(t.ruins) && !(t.building)','t.building = \'yes\''],
+        ['(t.landuse == \'built_up_area\' || t.place == \'settlement\') && t.building','t[\'settlement:type\'] = t.building; delete t.building'],
+        ['t.leisure == \'stadium\'','t.building = \'yes\''],
+        ['t[\'material:vertical\']','t.material = t[\'material:vertical\']; delete t[\'material:vertical\']'],
+        ['t[\'monitoring:weather\'] == \'yes\'','t.man_made = \'monitoring_station\''],
+        ['t.natural ==\'spring\' && t[\'spring:type\'] == \'spring\'','delete t[\'spring:type\']'],
+        ['t.product && t.man_made == \'storage_tank\'','t.content = t.product; delete t.product'],
+        ['t.public_transport == \'station\' && t[\'transport:type\'] == \'railway\'','t.railway = \'station\''],
+        ['t.public_transport == \'station\' && t[\'transport:type\'] == \'bus\'','t.bus = \'yes\''],
+        ['t.pylon ==\'yes\' && t[\'cable:type\'] == \'cableway\'',' t.aerialway = \'pylon\''],
+        ['t.pylon ==\'yes\' && t[\'cable:type\'] == \'power\'',' t.power = \'tower\''],
+        ['t.service == \'yard\'','t.railway = \'yes\''],
+        ['t.service == \'siding\'','t.railway = \'yes\''],
+        ['t.social_facility','t.amenity = \'social_facility\'; t[\'social_facility:for\'] = t.social_facility; t.social_facility = \'shelter\''],
+        ['t[\'tower:material\']','t.material = t[\'tower:material\']; delete t[\'tower:material\']'],
+        ['t[\'tower:type\'] && !(t.man_made)','t.man_made = \'tower\''],
+        ['t.use == \'islamic_prayer_hall\' && !(t.amenity)','t.amenity = \'place_of_worship\''],
+        ['t.water || t.landuse == \'basin\'','t.natural = \'water\''],
+        ['t.waterway == \'flow_control\'','t.flow_control = \'sluice_gate\''],
+        ['t.waterway == \'vanishing_point\' && t[\'water:sink:type\'] == \'sinkhole\'','t.natural = \'sinkhole\'; delete t.waterway; delete t[\'water:sink:type\']'],
+        ['t.wetland && !(t.natural)','t.natural = \'wetland\'']
+      ];
+
+      tds40.osmPostRules = translate.buildComplexRules(rulesList);
+    }
+
+    // translate.applyComplexRules(tags,attrs,tds40.osmPostRules);
+    // Pulling this out of translate
+    for (var i = 0, rLen = tds40.osmPostRules.length; i < rLen; i++)
+    {
+      if (tds40.osmPostRules[i][0](tags)) tds40.osmPostRules[i][1](tags,attrs);
+    }
+    // ##############
 
-        for (var col in attrs)
-        {
-            // Sort out FCODE funkyness:  f_CODE, F_Code etc
-            var tKey = col.toLowerCase();
-            tKey = tKey.replace(/\s/g, '').replace(/_/g, '');;
+    // Additional rules for particular FCODE's
+    switch (attrs.F_CODE)
+    {
+    case undefined: // Break early if no value. Should not get here.....
+      break;
 
-            if (tKey == 'fcode' && col !== 'F_CODE')
-            {
-                attrs.F_CODE = attrs[col];
-                delete attrs[col];
-                continue;
-            }
+    case 'AP030':
+      /* Now sort out Roads
+                HCT, TYP, RTY etc are related. No easy way to use one2one rules
 
-            // Check for an FCODE as a tag
-            if (col in tds40.fcodeLookup['F_CODE'])
-            {
-                attrs.F_CODE = col;
-                delete attrs[col];
-                continue;
-            }
+                HCT : Thoroughfare Class - TDS 3.0
+                TYP ; Thoroughfare Type - TDS 3.0 & 4.0
+                RTN_ROI: Route Designation - TDS 4.0
+                RIN_ROI: Route Designation - TDS 5.0 & 6.0
+                RTY: Roadway Type - TDS 5.0 & 6.0
 
-            // Stuff to be ignored or that gets swapped later - See applyToOsmPreProcessing
-            if (~tds40.rules.ignoreList.indexOf(col)) continue;
+                TDS3   TDS4       TDS5       TDS6
+                HCT -> RTN_ROI -> RIN_ROI -> RIN_ROI
+                TYP -> TYP     -> RTY     -> RTY
+                */
 
-            // Look for Attributes
-            if (col in tds40.rules.numBiased) continue;
+      // Set a Default: "It is a road but we don't know what it is"
+      tags.highway = 'road';
+
+      // This was a heap of if, else if, else if etc
+      if (tags['ref:road:type'] == 'motorway' || tags['ref:road:class'] == 'national_motorway')
+      {
+        tags.highway = 'motorway';
+        break;
+      }
+
+      if (tags['ref:road:type'] == 'limited_access_motorway' || tags['ref:road:class'] == 'primary')
+      {
+        tags.highway = 'trunk';
+        break;
+      }
+
+      if (tags['ref:road:type'] == 'parkway' || tags['ref:road:class'] == 'secondary')
+      {
+        tags.highway = 'primary';
+        break;
+      }
+
+      if (tags['ref:road:class'] == 'local')
+      {
+        tags.highway = 'secondary';
+        break;
+      }
+
+      if (tags['ref:road:type'] == 'road' || tags['ref:road:type'] == 'boulevard')
+      {
+        tags.highway = 'tertiary';
+        break;
+      }
+
+      if (tags['ref:road:type'] == 'street')
+      {
+        tags.highway = 'unclassified';
+        break;
+      }
+
+      if (tags['ref:road:type'] == 'lane')
+      {
+        tags.highway = 'service';
+        break;
+      }
+
+      // Other should get picked up by the OTH field
+      if (tags['ref:road:type'] == 'other')
+      {
+        tags.highway = 'road';
+        break;
+      }
+
+      // Catch all for the rest of the ref:road:type: close, circle drive etc
+      if (tags['ref:road:type'])
+      {
+        tags.highway = 'residential';
+        break;
+      }
+
+      // If we get this far then we are going with the default.
+      break;
+
+    case 'AA052': // Fix oil/gas/petroleum fields
+      switch (tags.product)
+      {
+      case undefined:
+        break;
+
+      case 'gas':
+        tags.industrial = 'gas';
+        break;
+
+      case 'petroleum':
+        tags.industrial = 'oil';
+        break;
+      }
+      break;
+
+      // AK030 - Amusement Parks
+      // F_CODE translation == tourism but FFN translation could be leisure
+      // E.g. water parks
+    case 'AK030':
+      if (tags.leisure && tags.tourism) delete tags.tourism;
+      break;
+
+    } // End switch F_CODE
+
+    // Road & Railway Crossings
+    // Road/Rail = crossing
+    // Road + Rail = level_crossing
+    if (tags.crossing_point)
+    {
+      if (tags['transport:type'] == 'railway')
+      {
+        tags.railway = 'crossing';
 
-            if (col in tds40.rules.txtBiased) continue;
+        if (tags['transport:type:2'] == 'road') tags.railway = 'level_crossing';
+      }
+      else if (tags['transport:type'] == 'road')
+      {
+        if (tags['transport:type:2'] == 'railway')
+        {
+          tags.railway = 'level_crossing';
+        }
+        else
+        {
+          tags.highway = 'crossing';
+        }
+      }
+    } // End crossing_point
 
-            if (col in tds40.lookup) continue;
+    // Add a building tag to Buildings and Fortified Buildings if we don't have one
+    // We can't do this in the funky rules function as it uses "attrs" _and_ "tags"
+    if ((attrs.F_CODE == 'AL013' || attrs.F_CODE == 'AH055') && !(tags.building)) tags.building = 'yes';
 
-            // Drop the "GEOM" attribute
-            if (col == 'GEOM')
+    if (tags.building == 'yes')
+    {
+      // Fix the building 'use' tag. If the building has a 'use' and no specific building tag. Give it one
+      if (tags.use && ((tags.use.indexOf('manufacturing') > -1) || (tags.use.indexOf('processing') > -1))) tags.building = 'industrial';
+      /*
+            else if (tags.use in facilityList)
             {
-                delete attrs[col];
-                continue;
+                tags.building = facilityList[tags.use];
+                // delete tags.use;
             }
+       */
 
-            // Not an Attribute so push it to the tags object
-            tags[col] = attrs[col];
-            delete attrs[col];
-        }
-
-    }, // End untangleAttributes
+      // Undo the blanket AL013/AL055 building assignment if required
+      if (tags.military == 'bunker') delete tags.building;
+    }
 
+    // Education:
+    if (tags['isced:level'] || tags.use == 'education')
+    {
+      if (tags.building == 'yes')
+      {
+        tags.building = 'school';
+      }
+      else if (tags.facility)
+      {
+        tags.amenity = 'school';
+      }
+    }
+
+    if (tags.use == 'vocational_education')
+    {
+      if (tags.building == 'yes')
+      {
+        tags.building = 'college';
+      }
+      else if (tags.facility)
+      {
+        tags.amenity = 'college';
+      }
+    }
+
+    // A facility is an area. Therefore "use" becomes "amenity". "Building" becomes "landuse"
+    if (tags.facility && tags.use)
+    {
+      if ((tags.use.indexOf('manufacturing') > -1) || (tags.use.indexOf('processing') > -1)) tags.man_made = 'works';
+      /*
+            else if (tags.use in facilityList)
+            {
+                tags.amenity = facilityList[tags.use];
+            }
+        */
+    }
 
-// #####################################################################################################
-    // ##### Start of the xxToOsmxx Block #####
-    applyToOsmPreProcessing: function(attrs, layerName, geometryType)
+    // Fix up landuse tags
+    if (attrs.F_CODE == 'AL020')
+    {
+      switch (tags.use) // Fixup the landuse tags
+      {
+      case undefined: // Break early if no value
+        break;
+
+      case 'commercial':
+        tags.landuse = 'commercial';
+        delete tags.use;
+        break;
+
+      case 'industrial':
+        tags.landuse = 'industrial';
+        delete tags.use;
+        break;
+
+      case 'residential':
+        tags.landuse = 'residential';
+        delete tags.use;
+        break;
+      } // End switch
+    }
+
+    // Fix lifecycle tags
+    switch (tags.condition)
+    {
+    case undefined: // Break early if no value
+      break;
+
+    case 'construction':
+      for (var typ of ['highway','bridge','railway','building'])
+      {
+        if (tags[typ])
+        {
+          tags.construction = tags[typ];
+          tags[typ] = 'construction';
+          delete tags.condition;
+          break;
+        }
+      }
+      break;
+
+    case 'abandoned':
+      for (var typ of ['highway','bridge','railway','building'])
+      {
+        if (tags[typ])
+        {
+          tags['abandoned:' + typ] = tags[typ];
+          delete tags[typ];
+          delete tags.condition;
+          break;
+        }
+      }
+      break;
+
+    case 'dismantled':
+      for (var typ of ['highway','bridge','railway','building'])
+      {
+        if (tags[typ])
+        {
+          tags['demolished:' + typ] = tags[typ];
+          delete tags[typ];
+          delete tags.condition;
+          break;
+        }
+      }
+      break;
+    } // End switch condifion
+
+    // Denominations without religions - from ZI037_REL which has some denominations as religions
+    if (tags.denomination)
     {
-        // Drop the FCSUBTYPE since we don't use it
-        if (attrs.FCSUBTYPE) delete attrs.FCSUBTYPE;
+      switch (tags.denomination)
+      {
+      case 'roman_catholic':
+      case 'orthodox':
+      case 'protestant':
+      case 'chaldean_catholic':
+      case 'nestorian': // Not sure about this
+        tags.religion = 'christian';
+        break;
+
+      case 'shia':
+      case 'sunni':
+        tags.religion = 'muslim';
+        break;
+      } // End switch
+    }
+
+    // Religious buildings: Church, Pagoda, Temple etc
+    if (attrs.ZI037_REL && tags.amenity !== 'place_of_worship')
+    {
+      tags.amenity = 'place_of_worship';
+    }
 
-        // List of data values to drop/ignore
-        var ignoreList = { '-999999.0':1,'-999999':1,'noinformation':1 };
+    // Fords and Roads
+    // Putting this on hold
+    // if (attrs.F_CODE == 'BH070' && !(tags.highway)) tags.highway = 'road';
+    // if ('ford' in tags && !(tags.highway)) tags.highway = 'road';
 
-        // List of attributes that can't have '0' as a value
-        var noZeroList = ['BNF','DZC','LC1','LC2','LC3','LC4','LTN','NOS','NPL','VST','WD1','WD2','WT2','ZI016_WD1'];
+    // Fix up areas
+    // The thought is: If Hoot thinks it's an area but OSM doesn't think it's an area, make it an area
+    if (geometryType == 'Area' && ! translate.isOsmArea(tags))
+    {
+      // Debug
+      // print('Adding area=yes');
+      tags.area = 'yes';
+    }
 
-        // This is a handy loop. We use it to:
-        // 1) Remove all of the "No Information" and -999999 fields
-        // 2) Convert all of the Attrs to uppercase - if needed
-        // 3) Swap some of the funky named attrs around
-        for (var col in attrs)
+    /* Putting this on hold as it will impact conflation
+        // Tweek the "abandoned" tag. Should this be extended to "destroyed" as well?
+        if (tags.condition == 'abandoned')
         {
-            // slightly ugly but we would like to account for: 'No Information','noInformation' etc
-            // First, push to lowercase
-            var attrValue = attrs[col].toString().toLowerCase();
-
-            // Get rid of the spaces in the text
-            attrValue = attrValue.replace(/\s/g, '');
+            abandonedList = ['amenity','shop','highway','tourism','leisure','building'];
 
-            // Wipe out the useless values
-            if (attrs[col] == '' || attrValue in ignoreList || attrs[col] in ignoreList)
-            {
-                delete attrs[col]; // debug: Comment this out to leave all of the No Info stuff in for testing
-                continue;
-            }
+            print('XX In Abandoned');
 
-            // Remove attributes with '0' values if they can't be '0'
-            if (noZeroList.indexOf(col) > -1 && attrs[col] == '0')
+            for (var i = 0, len = abandonedList.length; i < len; i++)
             {
-                delete attrs[col];
-                continue;
+                if (tags[abandonedList[i]])
+                {
+                    print('Tags: ' + abandonedList[i]);
+                    tags['abandoned:' + abandonedList[i]] = tags[abandonedList[i]];
+                    delete tags[abandonedList[i]];
+                    delete tags.condition;
+                    break;
+                }
             }
+        }
+    */
 
-            // Push the attribute to upper case - if needed
-            var c = col.toUpperCase();
-            if (c !== col)
-            {
-                attrs[c] = attrs[col];
-                delete attrs[col];
-                col = c; // For the rest of this loop iteration
-            }
+    // Bunkers. Are they actually Military?
+    if (tags.man_made == 'bunker' && tags.controlling_authority)
+    {
+      if (tags.controlling_authority == 'military' || tags.controlling_authority == '')
+      {
+        // Debug
+        print('Bunker: drop man_made. military = ' + tags.military);
+        tags.military = 'bunker';
+        delete tags.man_made;
+      }
+    }
 
-            // Now see if we need to swap attr names
-            if (col in tds40.rules.swapListIn)
-            {
-                // Debug:
-                // print('Swapped: ' + tds40.rules.swapList[i]);
-                attrs[tds40.rules.swapListIn[col]] = attrs[col];
-                delete attrs[col];
-                continue;
-            }
+    // Catch all. Particularly for Hardened Aircraft Shelters
+    if (tags.bunker_type && !(tags.man_made == 'bunker' || tags.military == 'bunker')) tags.military = 'bunker';
 
-            // The following is to account for TDSv30 vs TDSv40 attribute naming. Somehow
-            // they had the bright idea to rename XXX1 to XXX for a stack of features:
-            // E.g. FFN1 -> FFN
-            var endChar = col.charAt(col.length - 1);
-            if (endChar == 1 && ['LC1','ZI016_WD1','ZI020_FI1','MGL1'].indexOf(col) == -1)
-            {
-                attrs[col.slice(0,-1)] = attrs[col]
-                // Debug:
-                // print('Swapped: ' + col);
-                delete attrs[col];
-                continue;
-            }
-        } // End in attrs loop
+  }, // End of applyToOsmPostProcessing
 
-        // Drop all of the XXX Closure default values IFF the associated attributes are not set
-        // Doing this after the main cleaning loop so all of the -999999 values are
-        // already gone and we can just check for existance
-        for (var i in tds40.rules.closureList)
-        {
-            if (attrs[i])
-            {
-                if (attrs[tds40.rules.closureList[i][0]] || attrs[tds40.rules.closureList[i][1]])
-                {
-                    continue;
-                }
-                else
-                {
-                    delete attrs[i];
-                }
-            }
-        } // End closureList
+  // ##### End of the xxToOsmxx Block #####
 
-        // Now find an F_CODE
-        if (attrs.F_CODE)
-        {
-            // Drop the the "Not Found" F_CODE. This is from the UI
-            if (attrs.F_CODE == 'Not found') delete attrs.F_CODE;
-        }
-        else if (attrs.FCODE)
-        {
-            attrs.F_CODE = attrs.FCODE;
-            delete attrs.FCODE;
-        }
-        else
-        {
-            // Time to find an FCODE based on the filename
-            // Funky but it makes life easier
-            var llayerName = layerName.toString().toLowerCase();
+  // #####################################################################################################
 
-            for (var row in tds40.rules.fCodeMap)
-            {
-                for (var val in tds40.rules.fCodeMap[row][1])
-                {
-                    if (llayerName == tds40.rules.fCodeMap[row][1][val])
-                    {
-                        attrs.F_CODE = tds40.rules.fCodeMap[row][0];
-                        break;
-                    }
-                }
-            }
-        } // End of Find an FCode
+  // ##### Start of the xxToTdsxx Block #####
 
-    }, // End of applyToOsmPreProcessing
+  applyToTdsPreProcessing: function(tags, attrs, geometryType)
+  {
+    // Remove Hoot assigned tags for the source of the data
+    if (tags['source:ingest:datetime']) delete tags['source:ingest:datetime'];
+    if (tags.area) delete tags.area;
+    if (tags['error:circular']) delete tags['error:circular'];
+    if (tags['hoot:status']) delete tags['hoot:status'];
 
+    // Initial cleanup
+    for (var i in tags)
+    {
+      // Remove empty tags
+      if (tags[i] == '')
+      {
+        delete tags[i];
+        continue;
+      }
+
+      // Convert "abandoned:XXX" and "disused:XXX"features
+      if ((i.indexOf('abandoned:') !== -1) || (i.indexOf('disused:') !== -1))
+      {
+        // Hopeing there is only one ':' in the tag name...
+        var tList = i.split(':');
+        tags[tList[1]] = tags[i];
+        tags.condition = 'abandoned';
+        delete tags[i];
+        continue;
+      }
+
+      // Convert "demolished:XXX" features
+      if (i.indexOf('demolished:') !== -1)
+      {
+        // Hopeing there is only one ':' in the tag name...
+        var tList = i.split(':');
+        tags[tList[1]] = tags[i];
+        tags.condition = 'dismantled';
+        delete tags[i];
+        continue;
+      }
+    } // End Cleanup loop
+
+    // Fix Bunkers. Putting this first to skip the building=* rules
+    if (tags.building == 'bunker')
+    {
+      tags.military = 'bunker';
+      delete tags.building;
+    }
 
-// #####################################################################################################
-    applyToOsmPostProcessing : function (attrs, tags, layerName, geometryType)
+    // Fortified buildings vs Surface Bunkers
+    if (tags.military == 'bunker')
     {
-        // Unpack the ZI006_MEM field
-        if (tags.note)
-        {
-            var tObj = translate.unpackMemo(tags.note);
+      // Making a guess that these are military...
+      if (! tags.controlling_authority) tags.controlling_authority = 'military';
+
+      if (tags['bunker_type'] == 'munitions')
+      {
+        attrs.F_CODE = 'AM060'; // Surface Bunker
+        attrs.PPO = '3'; // Ammunition
+        delete tags.military;
+        delete tags['bunker_type'];
+      }
+    }
+
+    // Lifecycle: This is a bit funky and should probably be done with a fancy function instead of
+    // repeating the code
+    switch (tags.highway)
+    {
+    case undefined: // Break early if no value
+      break;
+
+    case 'abandoned':
+    case 'disused':
+      tags.highway = 'road';
+      tags.condition = 'abandoned';
+      break;
+
+    case 'demolished':
+      tags.highway = 'road';
+      tags.condition = 'dismantled';
+      break;
+
+    case 'construction':
+      if (tags.construction)
+      {
+        tags.highway = tags.construction;
+        delete tags.construction;
+      }
+      else
+      {
+        tags.highway = 'road';
+      }
+      tags.condition = 'construction';
+      break;
+    }
+
+    switch (tags.railway)
+    {
+    case undefined: // Break early if no value
+      break;
+
+    case 'abandoned':
+    case 'disused':
+      tags.railway = 'rail';
+      tags.condition = 'abandoned';
+      break;
+
+    case 'demolished':
+      tags.railway = 'yes';
+      tags.condition = 'dismantled';
+      break;
+
+    case 'construction':
+      if (tags.construction)
+      {
+        tags.railway = tags.construction;
+        delete tags.construction;
+      }
+      else
+      {
+        tags.railway = 'rail';
+      }
+      tags.condition = 'construction';
+      break;
+    }
+
+    switch (tags.building)
+    {
+    case undefined: // Break early if no value
+      break;
+
+    case 'abandoned':
+    case 'disused':
+      tags.building = 'yes';
+      tags.condition = 'abandoned';
+      break;
+
+    case 'destroyed':
+      tags.building = 'yes';
+      tags.condition = 'destroyed';
+      break;
+
+    case 'demolished':
+      tags.building = 'yes';
+      tags.condition = 'dismantled';
+      break;
+
+    case 'construction':
+      if (tags.construction)
+      {
+        tags.building = tags.construction;
+        delete tags.construction;
+      }
+      else
+      {
+        tags.building = 'yes';
+      }
+      tags.condition = 'construction';
+      break;
+    }
+
+    switch (tags.bridge)
+    {
+    case undefined: // Break early if no value
+      break;
+
+    case 'abandoned':
+    case 'disused':
+      tags.bridge = 'yes';
+      tags.condition = 'abandoned';
+      break;
+
+    case 'destroyed':
+      tags.bridge = 'yes';
+      tags.condition = 'destroyed';
+      break;
+
+    case 'demolished':
+      tags.bridge = 'yes';
+      tags.condition = 'dismantled';
+      break;
+
+    case 'construction':
+      if (tags.construction)
+      {
+        tags.bridge = tags.construction;
+        delete tags.construction;
+      }
+      else
+      {
+        tags.bridge = 'yes';
+      }
+      tags.condition = 'construction';
+      break;
+    }
+
+    if (tds40.PreRules == undefined)
+    {
+      // See ToOsmPostProcessing for more details about rulesList
+      var rulesList = [
+        ['t.amenity == \'bus_station\'','t.public_transport = \'station\'; t[\'transport:type\'] = \'bus\''],
+        // ["t.amenity == 'marketplace'","t.facility = 'yes'"],
+        ['t.barrier == \'tank_trap\' && t.tank_trap == \'dragons_teeth\'','t.barrier = \'dragons_teeth\'; delete t.tank_trap'],
+        ['t.communication == \'line\'','t[\'cable:type\'] = \'communication\''],
+        ['t.content && !(t.product)','t.product = t.content; delete t.content'],
+        ['t.construction && t.highway','t.highway = t.construction; t.condition = \'construction\'; delete t.construction'],
+        ['t.construction && t.railway','t.railway = t.construction; t.condition = \'construction\'; delete t.construction'],
+        ['t.control_tower && t.man_made == \'tower\'','delete t.man_made'],
+        ['t.diplomatic && t.amenity == \'embassy\'','delete t.amenity'],
+        ['t.highway == \'bus_stop\'','t[\'transport:type\'] = \'bus\''],
+        ['t.highway == \'crossing\'','t[\'transport:type\'] = \'road\';a.F_CODE = \'AQ062\'; delete t.highway'],
+        ['t.highway == \'give-way\'','a.F_CODE = \'AQ062\''],
+        ['t.highway == \'mini_roundabout\'','t.junction = \'roundabout\''],
+        ['t.highway == \'steps\'','t.highway = \'footway\''],
+        ['t.highway == \'stop\'','a.F_CODE = \'AQ062\''],
+        ['t.historic == \'castle\' && t.building','delete t.building'],
+        ['t.historic == \'castle\' && t.ruins == \'yes\'','t.condition = \'destroyed\'; delete t.ruins'],
+        ['t.landcover == \'snowfield\' || t.landcover == \'ice-field\'','a.F_CODE = \'BJ100\''],
+        ['t.landuse == \'farmland\' && t.crop == \'fruit_tree\'','t.landuse = \'orchard\''],
+        ['t.landuse == \'railway\' && t[\'railway:yard\'] == \'marshalling_yard\'','a.F_CODE = \'AN060\''],
+        ['t.landuse == \'reservoir\'','t.water = \'reservoir\'; delete t.landuse'],
+        ['t.landuse == \'scrub\'','t.natural = \'scrub\'; delete t.landuse'],
+        ['t.launch_pad','delete t.launch_pad; t.aeroway=\'launchpad\''],
+        ['t.leisure == \'recreation_ground\'','t.landuse = \'recreation_ground\'; delete t.leisure'],
+        ['t.leisure == \'sports_centre\'','t.facility = \'yes\'; t.use = \'recreation\'; delete t.leisure'],
+        ['t.leisure == \'stadium\' && t.building','delete t.building'],
+        ['t.man_made && t.building == \'yes\'','delete t.building'],
+        ['t.man_made == \'launch_pad\'','delete t.man_made; t.aeroway=\'launchpad\''],
+        ['t.median == \'yes\'','t.is_divided = \'yes\''],
+        ['t.military == \'barracks\'','t.use = \'dormitory\''],
+        ['t.natural == \'desert\' && t.surface','t.desert_surface = t.surface; delete t.surface'],
+        ['t.natural == \'sinkhole\'','a.F_CODE = \'BH145\'; t[\'water:sink:type\'] = \'sinkhole\'; delete t.natural'],
+        ['t.natural == \'spring\' && !(t[\'spring:type\'])','t[\'spring:type\'] = \'spring\''],
+        ['t.natural == \'wood\'','t.landuse = \'forest\'; delete t.natural'],
+        ['t.power == \'pole\'','t[\'cable:type\'] = \'power\';t[\'tower:shape\'] = \'pole\''],
+        ['t.power == \'tower\'','t[\'cable:type\'] = \'power\'; t.pylon = \'yes\'; delete t.power'],
+        ['t.power == \'line\'','t[\'cable:type\'] = \'power\', t.cable = \'yes\'; delete t.power'],
+        ['t.power == \'generator\'','t.use = \'power_generation\'; a.F_CODE = \'AL013\''],
+        ['t.rapids == \'yes\'','t.waterway = \'rapids\'; delete t.rapids'],
+        ['t.railway == \'station\'','t.public_transport = \'station\';  t[\'transport:type\'] = \'railway\''],
+        ['t.railway == \'level_crossing\'','t[\'transport:type\'] = \'railway\';t[\'transport:type:2\'] = \'road\'; a.F_CODE = \'AQ062\'; delete t.railway'],
+        ['t.railway == \'crossing\'','t[\'transport:type\'] = \'railway\'; a.F_CODE = \'AQ062\'; delete t.railway'],
+        ['t.resource','t.raw_material = t.resource; delete t.resource'],
+        ['t.route == \'road\' && !(t.highway)','t.highway = \'road\'; delete t.route'],
+        // ["(t.shop || t.office) &&  !(t.building)","a.F_CODE = 'AL013'"],
+        ['t.social_facility == \'shelter\'','t.social_facility = t[\'social_facility:for\']; delete t.amenity; delete t[\'social_facility:for\']'],
+        ['t[\'tower:type\'] == \'minaret\' && t.man_made == \'tower\'','delete t.man_made'],
+        ['t.tunnel == \'building_passage\'','t.tunnel = \'yes\''],
+        ['t.use == \'islamic_prayer_hall\' && t.amenity == \'place_of_worship\'','delete t.amenity'],
+        ['!(t.water) && t.natural == \'water\'','t.water = \'lake\''],
+        ['t.wetland && t.natural == \'wetland\'','delete t.natural'],
+        ['t.water == \'river\'','t.waterway = \'river\''],
+        ['t.waterway == \'riverbank\'','t.waterway = \'river\'']
+      ];
+
+      tds40.PreRules = translate.buildComplexRules(rulesList);
+    }
+
+    // Apply the rulesList
+    // translate.applyComplexRules(tags,attrs,tds40.PreRules);
+    // Pulling this out of translate
+    for (var i = 0, rLen = tds40.PreRules.length; i < rLen; i++)
+    {
+      if (tds40.PreRules[i][0](tags)) tds40.PreRules[i][1](tags,attrs);
+    }
 
-            if (tObj.tags !== '')
-            {
-                var tTags = JSON.parse(tObj.tags)
-                for (i in tTags)
-                {
-                    // Debug
-                    // print('Memo: Add: ' + i + ' = ' + tTags[i]);
-                    if (tags[tTags[i]]) hoot.logWarn('Unpacking ZI006_MEM, overwriting ' + i + ' = ' + tags[i] + '  with ' + tTags[i]);
-                    tags[i] = tTags[i];
-                }
-            }
+    // Fix up OSM 'walls' around facilities
+    if ((tags.barrier == 'wall' || tags.barrier == 'fence') && geometryType == 'Area')
+    {
+      if (tags.landuse == 'military' || tags.military)
+      {
+        attrs.F_CODE = 'SU001'; // Military Installation
+      }
+      else
+      {
+        attrs.F_CODE = 'AL010'; // Facility
+      }
+
+      delete tags.barrier; // Take away the walls...
+    }
+
+    // going out on a limb and processing OSM specific tags:
+    // - Building == a thing,
+    // - Amenity == The area around a thing
+    // Note: amenity=place_of_worship is a special case. It _should_ have an associated building tag
+    var facilityList = {'school':'850','university':'855','college':'857','hospital':'860'};
+
+    if (tags.amenity in facilityList)
+    {
+      if (geometryType == 'Area')
+      {
+        attrs.F_CODE = 'AL010'; // Facility
+
+        // If the user has also set a building tag, delete it
+        if (tags.building) delete tags.building;
+      }
+      else
+      {
+        // Make sure we don't turn point facilities into buildings
+        if (tags.facility !== 'yes')
+        {
+          // Debug
+          // print('Making a building: ' + tags.facility);
+          attrs.F_CODE = 'AL013'; // Building
+        }
+      }
+
+      // If we don't have a Feature Function then assign one
+      if (! attrs.FFN)
+      {
+        attrs.FFN = facilityList[tags.amenity];
+        // Debug
+        // print('PreDropped: amenity = ' + tags.amenity);
+        delete tags.amenity;
+      }
+    }
 
-            if (tObj.text && tObj.text !== '')
-            {
-                tags.note = tObj.text;
-            }
-            else
-            {
-                delete tags.note;
-            }
-        } // End unpack tags.note
+    // Adding a custom rule for malls to override the rules above
+    // All of the other "shops" are buildings
+    if (tags.shop == 'mall') attrs.F_CODE = 'AG030';
 
-        // Add the LayerName to the source
-        if ((! tags.source) && layerName !== '') tags.source = 'tdsv40:' + layerName.toLowerCase();
+    // Churches etc
+    if (tags.building && ! tags.amenity)
+    {
+      var how = [ 'church','chapel','cathedral','mosque','pagoda','shrine','temple',
+        'synagogue','tabernacle','stupa'];
+      if (how.indexOf(tags.building) > -1)
+      {
+        tags.amenity = 'place_of_worship';
+      }
+
+      var rc = [ 'mission','religious_community','seminary','convent','monastry',
+        'noviciate','hermitage','retrest','marabout'];
+      if (rc.indexOf(tags.building) > -1)
+      {
+        tags.use = 'religious_activities';
+      }
+    }
+
+    // Fix up water features from OSM
+    if (tags.natural == 'water' && !(tags.water))
+    {
+      if (geometryType =='Line')
+      {
+        tags.waterway = 'river';
+        attrs.F_CODE = 'BH140';
+      }
+      else
+      {
+        tags.water = 'lake';
+        attrs.F_CODE = 'BH082';
+      }
+    }
+
+    // Cutlines/Cleared Ways & Highways
+    // This might need a cleanup
+    if (tags.man_made == 'cutline' && tags.highway)
+    {
+      if (geometryType == 'Area')
+      {
+        // Keep the cutline/cleared way
+        attrs.F_CODE = 'EC040';
 
-        // If we have a UFI, store it. Some of the MAAX data has a LINK_ID instead of a UFI
-        if (tags.uuid)
+        if (tags.memo)
         {
-            tags.uuid = '{' + tags['uuid'].toString().toLowerCase() + '}';
+          tags.memo = tags.memo + ';highway:' + tags.highway;
         }
         else
         {
-            if (tds40.configIn.OgrAddUuid == 'true') tags.uuid = createUuid();
+          tags.memo = 'highway:' + tags.highway;
         }
 
-        if (tds40.osmPostRules == undefined)
+        delete tags.highway;
+      }
+      else
+      {
+        // Drop the cutline/cleared way
+        delete tags.man_made;
+
+        if (tags.memo)
         {
-            // ##############
-            // A "new" way of specifying rules. Jason came up with this while playing around with NodeJs
-            //
-            // Rules format:  ["test expression","output result"];
-            // Note: t = tags, a = attrs and attrs can only be on the RHS
-            var rulesList = [
-            ["t.barrier == 'dragons_teeth' && !(t.tank_trap)","t.barrier = 'tank_trap'; t.tank_trap = 'dragons_teeth'"],
-            ["t.amenity == 'stop' && t['transport:type'] == 'bus'","t.highway = 'bus_stop';"],
-            ["t.boundary == 'protected_area' && !(t.protect_class)","t.protect_class = '4';"],
-            ["t['bridge:movable'] && t['bridge:movable'] !== 'no' && t['bridge:movable'] !== 'unknown'","t.bridge = 'movable'"],
-            ["t.cable =='yes' && t['cable:type'] == 'power'"," t.power = 'line'; delete t.cable; delete t['cable:type']"],
-            ["t.control_tower == 'yes' && t.use == 'air_traffic_control'","t['tower:type'] = 'observation'"],
-            ["t.desert_surface","t.surface = t.desert_surface; delete t.desert_surface"],
-            ["t.diplomatic && !(t.amenity)","t.amenity = 'embassy'"],
-            ["t['generator:source'] == 'wind'","t.power = 'generator'"],
-            ["t.historic == 'castle' && !(t.ruins) && !(t.building)","t.building = 'yes'"],
-            ["(t.landuse == 'built_up_area' || t.place == 'settlement') && t.building","t['settlement:type'] = t.building; delete t.building"],
-            ["t.leisure == 'stadium'","t.building = 'yes'"],
-            ["t['material:vertical']","t.material = t['material:vertical']; delete t['material:vertical']"],
-            ["t['monitoring:weather'] == 'yes'","t.man_made = 'monitoring_station'"],
-            ["t.natural =='spring' && t['spring:type'] == 'spring'","delete t['spring:type']"],
-            ["t.product && t.man_made == 'storage_tank'","t.content = t.product; delete t.product"],
-            ["t.public_transport == 'station' && t['transport:type'] == 'railway'","t.railway = 'station'"],
-            ["t.public_transport == 'station' && t['transport:type'] == 'bus'","t.bus = 'yes'"],
-            ["t.pylon =='yes' && t['cable:type'] == 'cableway'"," t.aerialway = 'pylon'"],
-            ["t.pylon =='yes' && t['cable:type'] == 'power'"," t.power = 'tower'"],
-            ["t.service == 'yard'","t.railway = 'yes'"],
-            ["t.service == 'siding'","t.railway = 'yes'"],
-            ["t.social_facility","t.amenity = 'social_facility'; t['social_facility:for'] = t.social_facility; t.social_facility = 'shelter'"],
-            ["t['tower:material']","t.material = t['tower:material']; delete t['tower:material']"],
-            ["t['tower:type'] && !(t.man_made)","t.man_made = 'tower'"],
-            ["t.use == 'islamic_prayer_hall' && !(t.amenity)","t.amenity = 'place_of_worship'"],
-            ["t.water || t.landuse == 'basin'","t.natural = 'water'"],
-            ["t.waterway == 'flow_control'","t.flow_control = 'sluice_gate'"],
-            ["t.waterway == 'vanishing_point' && t['water:sink:type'] == 'sinkhole'","t.natural = 'sinkhole'; delete t.waterway; delete t['water:sink:type']"],
-            ["t.wetland && !(t.natural)","t.natural = 'wetland'"]
-            ];
-
-            tds40.osmPostRules = translate.buildComplexRules(rulesList);
+          tags.memo = tags.memo + ';cleared_way:yes';
         }
+        else
+        {
+          tags.memo = 'cleared_way:yes';
+        }
+      }
+    }
 
-        // translate.applyComplexRules(tags,attrs,tds40.osmPostRules);
-        // Pulling this out of translate
-        for (var i = 0, rLen = tds40.osmPostRules.length; i < rLen; i++)
+    // Fix up landuse & AL020
+    switch (tags.landuse)
+    {
+    case undefined: // Break early if no value
+      break;
+
+    case 'brownfield':
+      tags.landuse = 'built_up_area';
+      tags.condition = 'destroyed';
+      break;
+
+    case 'commercial':
+    case 'retail':
+      tags.use = 'commercial';
+      tags.landuse = 'built_up_area';
+      break;
+
+    case 'construction':
+      tags.condition = 'construction';
+      tags.landuse = 'built_up_area';
+      break;
+
+    case 'farm':
+    case 'allotments':
+      tags.landuse = 'farmland';
+      break;
+
+    case 'farmyard': // NOTE: This is different to farm
+      tags.facility = 'yes';
+      tags.use = 'agriculture';
+      delete tags.landuse;
+      break;
+
+    case 'grass':
+      tags.natural = 'grassland';
+      tags['grassland:type'] = 'grassland';
+      delete tags.landuse;
+      break;
+
+    case 'industrial': // Deconflict with AA052 Hydrocarbons Field
+      switch (tags.industrial)
+      {
+      case undefined: // Built up Area
+        tags.use = 'industrial';
+        tags.landuse = 'built_up_area';
+        break;
+
+      case 'oil':
+        tags.product = 'petroleum';
+        tags.industrial = 'hydrocarbons_field';
+        delete tags.landuse;
+        break;
+
+      case 'gas':
+        tags.product = 'gas';
+        tags.industrial = 'hydrocarbons_field';
+        delete tags.landuse;
+        break;
+      }
+      break;
+
+    case 'military':
+      tags.military = 'installation';
+      delete tags.landuse;
+      break;
+
+    case 'meadow':
+      tags.natural = 'grassland';
+      tags['grassland:type'] = 'meadow';
+      delete tags.landuse;
+      break;
+
+    case 'residential':
+      tags.use = 'residential';
+      tags.landuse = 'built_up_area';
+      break;
+
+    } // End switch landuse
+
+    // Places, localities and regions
+    if (tags.place)
+    {
+      switch (tags.place)
+      {
+      case 'city':
+      case 'town':
+      case 'suburb':
+      case 'neighbourhood':
+      case 'quarter':
+      case 'village':
+      case 'hamlet':
+      case 'yes':  // We set this as a default when going to OSM
+        attrs.F_CODE = 'AL020'; // Built Up Area
+        delete tags.place;
+        break;
+
+      case 'isolated_dwelling':
+        attrs.F_CODE = 'AL105'; // Settlement
+        delete tags.place;
+        break;
+
+      case 'populated':
+      case 'state':
+      case 'county':
+      case 'region':
+      case 'locality':
+      case 'municipality':
+      case 'borough':
+      case 'unknown':
+        attrs.F_CODE = 'ZD045'; // Annotated Location
+        if (tags.memo)
+        {
+          tags.memo = tags.memo + ';annotation:' + tags.place;
+        }
+        else
         {
-            if (tds40.osmPostRules[i][0](tags)) tds40.osmPostRules[i][1](tags,attrs);
+          tags.memo = 'annotation:' + tags.place;
         }
-        // ##############
+        delete tags.place;
+        break;
 
-        // Additional rules for particular FCODE's
-        switch (attrs.F_CODE)
+      case 'island':
+      case 'islet':
+        // If we have a coastline around an Island, decide if we are going make an Island
+        // or a Coastline
+        if (tags.natural == 'coastline')
         {
-            case undefined: // Break early if no value. Should not get here.....
-                break;
+          if (geometryType == 'Line')
+          {
+            attrs.F_CODE = 'BA010'; // Land/Water Boundary - Line
+            delete tags.place;
+          }
+          else
+          {
+            // NOTE: Islands can be Points or Areas
+            attrs.F_CODE = 'BA030'; // Island
+            delete tags.natural;
+          }
+        }
+        break;
+      } // End switch
+    }
 
-            case 'AP030':
-                /* Now sort out Roads
-                HCT, TYP, RTY etc are related. No easy way to use one2one rules
+    // Bridges & Roads.  If we have an area or a line everything is fine
+    // If we have a point, we need to make sure that it becomes a bridge, not a road
+    if (tags.bridge && tags.bridge !== 'no' && geometryType =='Point') attrs.F_CODE = 'AQ040';
 
-                HCT : Thoroughfare Class - TDS 3.0
-                TYP ; Thoroughfare Type - TDS 3.0 & 4.0
-                RTN_ROI: Route Designation - TDS 4.0
-                RIN_ROI: Route Designation - TDS 5.0 & 6.0
-                RTY: Roadway Type - TDS 5.0 & 6.0
-
-                TDS3   TDS4       TDS5       TDS6
-                HCT -> RTN_ROI -> RIN_ROI -> RIN_ROI
-                TYP -> TYP     -> RTY     -> RTY
-                */
-
-                // Set a Default: "It is a road but we don't know what it is"
-                tags.highway = 'road';
-
-                // This was a heap of if, else if, else if etc
-                if (tags['ref:road:type'] == 'motorway' || tags['ref:road:class'] == 'national_motorway')
-                {
-                    tags.highway = 'motorway';
-                    break;
-                }
-
-                if (tags['ref:road:type'] == 'limited_access_motorway' || tags['ref:road:class'] == 'primary')
-                {
-                    tags.highway = 'trunk';
-                    break;
-                }
-
-                if (tags['ref:road:type'] == 'parkway' || tags['ref:road:class'] == 'secondary')
-                {
-                    tags.highway = 'primary';
-                    break;
-                }
-
-                if (tags['ref:road:class'] == 'local')
-                {
-                    tags.highway = 'secondary';
-                    break;
-                }
-
-                if (tags['ref:road:type'] == 'road' || tags['ref:road:type'] == 'boulevard')
-                {
-                    tags.highway = 'tertiary';
-                    break;
-                }
-
-                if (tags['ref:road:type'] == 'street')
-                {
-                    tags.highway = 'unclassified';
-                    break;
-                }
-
-                if (tags['ref:road:type'] == 'lane')
-                {
-                    tags.highway = 'service';
-                    break;
-                }
-
-                // Other should get picked up by the OTH field
-                if (tags['ref:road:type'] == 'other')
-                {
-                    tags.highway = 'road';
-                    break;
-                }
-
-                // Catch all for the rest of the ref:road:type: close, circle drive etc
-                if (tags['ref:road:type'])
-                {
-                    tags.highway = 'residential';
-                    break;
-                }
-
-                // If we get this far then we are going with the default.
-                break;
-
-            case 'AA052': // Fix oil/gas/petroleum fields
-                switch (tags.product)
-                {
-                    case undefined:
-                        break;
-
-                    case 'gas':
-                        tags.industrial = 'gas';
-                        break;
-
-                    case 'petroleum':
-                        tags.industrial = 'oil';
-                        break;
-                }
-                break;
-
-            // AK030 - Amusement Parks
-            // F_CODE translation == tourism but FFN translation could be leisure
-            // E.g. water parks
-            case 'AK030':
-                if (tags.leisure && tags.tourism) delete tags.tourism;
-                break;
-
-        } // End switch F_CODE
-
-        // Road & Railway Crossings
-        // Road/Rail = crossing
-        // Road + Rail = level_crossing
-        if (tags.crossing_point)
-        {
-            if (tags['transport:type'] == 'railway')
-            {
-                tags.railway = 'crossing';
-
-                if (tags['transport:type:2'] == 'road') tags.railway = 'level_crossing';
-            }
-            else if (tags['transport:type'] == 'road')
-            {
-                if (tags['transport:type:2'] == 'railway')
-                {
-                    tags.railway = 'level_crossing';
-                }
-                else
-                {
-                    tags.highway = 'crossing';
-                }
-            }
-        } // End crossing_point
-
-        // Add a building tag to Buildings and Fortified Buildings if we don't have one
-        // We can't do this in the funky rules function as it uses "attrs" _and_ "tags"
-        if ((attrs.F_CODE == 'AL013' || attrs.F_CODE == 'AH055') && !(tags.building)) tags.building = 'yes';
-
-        if (tags.building == 'yes')
-        {
-            // Fix the building 'use' tag. If the building has a 'use' and no specific building tag. Give it one
-            if (tags.use && ((tags.use.indexOf('manufacturing') > -1) || (tags.use.indexOf('processing') > -1))) tags.building = 'industrial';
-      /*
-            else if (tags.use in facilityList)
-            {
-                tags.building = facilityList[tags.use];
-                // delete tags.use;
-            }
-       */
-
-            // Undo the blanket AL013/AL055 building assignment if required
-            if (tags.military == 'bunker') delete tags.building;
-        }
-
-        // Education:
-        if (tags['isced:level'] || tags.use == 'education')
-        {
-            if (tags.building == 'yes')
-            {
-                tags.building = 'school'
-            }
-            else if (tags.facility)
-            {
-                tags.amenity = 'school';
-            }
-        }
-
-        if (tags.use == 'vocational_education')
-        {
-            if (tags.building == 'yes')
-            {
-                tags.building = 'college'
-            }
-            else if (tags.facility)
-            {
-                tags.amenity = 'college';
-            }
-        }
-
-        // A facility is an area. Therefore "use" becomes "amenity". "Building" becomes "landuse"
-        if (tags.facility && tags.use)
-        {
-            if ((tags.use.indexOf('manufacturing') > -1) || (tags.use.indexOf('processing') > -1)) tags.man_made = 'works';
-       /*
-            else if (tags.use in facilityList)
-            {
-                tags.amenity = facilityList[tags.use];
-            }
-        */
-        }
-
-        // Fix up landuse tags
-        if (attrs.F_CODE == 'AL020')
-        {
-            switch (tags.use) // Fixup the landuse tags
-            {
-                case undefined: // Break early if no value
-                    break;
-
-                case 'commercial':
-                    tags.landuse = 'commercial';
-                    delete tags.use;
-                    break;
-
-                case 'industrial':
-                    tags.landuse = 'industrial';
-                    delete tags.use;
-                    break;
-
-                case 'residential':
-                    tags.landuse = 'residential';
-                    delete tags.use;
-                    break;
-            } // End switch
-        }
-
-        // Fix lifecycle tags
-        switch (tags.condition)
-        {
-            case undefined: // Break early if no value
-                break;
-
-            case 'construction':
-                for (var typ of ['highway','bridge','railway','building'])
-                {
-                    if (tags[typ])
-                    {
-                        tags.construction = tags[typ];
-                        tags[typ] = 'construction';
-                        delete tags.condition;
-                        break;
-                    }
-                }
-                break;
-
-            case 'abandoned':
-                for (var typ of ['highway','bridge','railway','building'])
-                {
-                    if (tags[typ])
-                    {
-                        tags['abandoned:' + typ] = tags[typ];
-                        delete tags[typ];
-                        delete tags.condition;
-                        break;
-                    }
-                }
-                break;
-
-            case 'dismantled':
-                for (var typ of ['highway','bridge','railway','building'])
-                {
-                    if (tags[typ])
-                    {
-                        tags['demolished:' + typ] = tags[typ];
-                        delete tags[typ];
-                        delete tags.condition;
-                    break;
-                    }
-                }
-                break;
-        } // End switch condifion
-
-        // Denominations without religions - from ZI037_REL which has some denominations as religions
-        if (tags.denomination)
-        {
-            switch (tags.denomination)
-            {
-                case 'roman_catholic':
-                case 'orthodox':
-                case 'protestant':
-                case 'chaldean_catholic':
-                case 'nestorian': // Not sure about this
-                    tags.religion = 'christian';
-                    break;
-
-                case 'shia':
-                case 'sunni':
-                    tags.religion = 'muslim';
-                    break;
-            } // End switch
-        }
-
-        // Religious buildings: Church, Pagoda, Temple etc
-        if (attrs.ZI037_REL && tags.amenity !== 'place_of_worship')
-        {
-            tags.amenity = 'place_of_worship';
-        }
-
-        // Fords and Roads
-        // Putting this on hold
-        // if (attrs.F_CODE == 'BH070' && !(tags.highway)) tags.highway = 'road';
-        // if ('ford' in tags && !(tags.highway)) tags.highway = 'road';
-
-        // Fix up areas
-        // The thought is: If Hoot thinks it's an area but OSM doesn't think it's an area, make it an area
-        if (geometryType == 'Area' && ! translate.isOsmArea(tags))
-        {
-            // Debug
-            // print('Adding area=yes');
-            tags.area = 'yes';
-        }
-
-        /* Putting this on hold as it will impact conflation
-        // Tweek the "abandoned" tag. Should this be extended to "destroyed" as well?
-        if (tags.condition == 'abandoned')
-        {
-            abandonedList = ['amenity','shop','highway','tourism','leisure','building'];
-
-            print('XX In Abandoned');
-
-            for (var i = 0, len = abandonedList.length; i < len; i++)
-            {
-                if (tags[abandonedList[i]])
-                {
-                    print('Tags: ' + abandonedList[i]);
-                    tags['abandoned:' + abandonedList[i]] = tags[abandonedList[i]];
-                    delete tags[abandonedList[i]];
-                    delete tags.condition;
-                    break;
-                }
-            }
-        }
-    */
-
-        // Bunkers. Are they actually Military?
-        if (tags.man_made == 'bunker' && tags.controlling_authority)
-        {
-            if (tags.controlling_authority == 'military' || tags.controlling_authority == '')
-            {
-                // Debug
-                print('Bunker: drop man_made. military = ' + tags.military);
-                tags.military = 'bunker';
-                delete tags.man_made;
-            }
-        }
-
-        // Catch all. Particularly for Hardened Aircraft Shelters
-        if (tags.bunker_type && !(tags.man_made == 'bunker' || tags.military == 'bunker')) tags.military = 'bunker';
-
-    }, // End of applyToOsmPostProcessing
-
-    // ##### End of the xxToOsmxx Block #####
-
-// #####################################################################################################
-
-    // ##### Start of the xxToTdsxx Block #####
-
-    applyToTdsPreProcessing: function(tags, attrs, geometryType)
-    {
-        // Remove Hoot assigned tags for the source of the data
-        if (tags['source:ingest:datetime']) delete tags['source:ingest:datetime'];
-        if (tags.area) delete tags.area;
-        if (tags['error:circular']) delete tags['error:circular'];
-        if (tags['hoot:status']) delete tags['hoot:status'];
-
-        // Initial cleanup
-        for (var i in tags)
-        {
-            // Remove empty tags
-            if (tags[i] == '')
-            {
-                delete tags[i];
-                continue;
-            }
-
-            // Convert "abandoned:XXX" and "disused:XXX"features
-            if ((i.indexOf('abandoned:') !== -1) || (i.indexOf('disused:') !== -1))
-            {
-                // Hopeing there is only one ':' in the tag name...
-                var tList = i.split(':');
-                tags[tList[1]] = tags[i];
-                tags.condition = 'abandoned';
-                delete tags[i];
-                continue;
-            }
-
-            // Convert "demolished:XXX" features
-            if (i.indexOf('demolished:') !== -1)
-            {
-                // Hopeing there is only one ':' in the tag name...
-                var tList = i.split(':');
-                tags[tList[1]] = tags[i];
-                tags.condition = 'dismantled';
-                delete tags[i];
-                continue;
-            }
-        } // End Cleanup loop
-
-        // Fix Bunkers. Putting this first to skip the building=* rules
-        if (tags.building == 'bunker')
-        {
-            tags.military = 'bunker';
-            delete tags.building;
-        }
-
-        // Fortified buildings vs Surface Bunkers
-        if (tags.military == 'bunker')
-        {
-            // Making a guess that these are military...
-            if (! tags.controlling_authority) tags.controlling_authority = 'military';
-
-            if (tags['bunker_type'] == 'munitions')
-            {
-                attrs.F_CODE = 'AM060'; // Surface Bunker
-                attrs.PPO = '3'; // Ammunition
-                delete tags.military;
-                delete tags['bunker_type'];
-            }
-        }
-
-        // Lifecycle: This is a bit funky and should probably be done with a fancy function instead of
-        // repeating the code
-        switch (tags.highway)
-        {
-            case undefined: // Break early if no value
-                break;
-
-            case 'abandoned':
-            case 'disused':
-                tags.highway = 'road';
-                tags.condition = 'abandoned';
-                break;
-
-            case 'demolished':
-                tags.highway = 'road';
-                tags.condition = 'dismantled';
-                break;
-
-            case 'construction':
-                if (tags.construction)
-                {
-                    tags.highway = tags.construction;
-                    delete tags.construction;
-                }
-                else
-                {
-                    tags.highway = 'road';
-                }
-                tags.condition = 'construction';
-                break;
-        }
-
-        switch (tags.railway)
-        {
-            case undefined: // Break early if no value
-                break;
-
-            case 'abandoned':
-            case 'disused':
-                tags.railway = 'rail';
-                tags.condition = 'abandoned';
-                break;
-
-            case 'demolished':
-                tags.railway = 'yes';
-                tags.condition = 'dismantled';
-                break;
-
-            case 'construction':
-                if (tags.construction)
-                {
-                    tags.railway = tags.construction;
-                    delete tags.construction;
-                }
-                else
-                {
-                    tags.railway = 'rail';
-                }
-                tags.condition = 'construction';
-                break;
-        }
-
-        switch (tags.building)
-        {
-            case undefined: // Break early if no value
-                break;
-
-            case 'abandoned':
-            case 'disused':
-                tags.building = 'yes';
-                tags.condition = 'abandoned';
-                break;
-
-            case 'destroyed':
-                tags.building = 'yes';
-                tags.condition = 'destroyed';
-                break;
-
-            case 'demolished':
-                tags.building = 'yes';
-                tags.condition = 'dismantled';
-                break;
-
-            case 'construction':
-                if (tags.construction)
-                {
-                    tags.building = tags.construction;
-                    delete tags.construction;
-                }
-                else
-                {
-                    tags.building = 'yes';
-                }
-                tags.condition = 'construction';
-                break;
-        }
-
-        switch (tags.bridge)
-        {
-            case undefined: // Break early if no value
-                break;
-
-            case 'abandoned':
-            case 'disused':
-                tags.bridge = 'yes';
-                tags.condition = 'abandoned';
-                break;
-
-            case 'destroyed':
-                tags.bridge = 'yes';
-                tags.condition = 'destroyed';
-                break;
-
-            case 'demolished':
-                tags.bridge = 'yes';
-                tags.condition = 'dismantled';
-                break;
-
-            case 'construction':
-                if (tags.construction)
-                {
-                    tags.bridge = tags.construction;
-                    delete tags.construction;
-                }
-                else
-                {
-                    tags.bridge = 'yes';
-                }
-                tags.condition = 'construction';
-                break;
-        }
-
-        if (tds40.PreRules == undefined)
-        {
-        // See ToOsmPostProcessing for more details about rulesList
-            var rulesList = [
-            ["t.amenity == 'bus_station'","t.public_transport = 'station'; t['transport:type'] = 'bus'"],
-            // ["t.amenity == 'marketplace'","t.facility = 'yes'"],
-            ["t.barrier == 'tank_trap' && t.tank_trap == 'dragons_teeth'","t.barrier = 'dragons_teeth'; delete t.tank_trap"],
-            ["t.communication == 'line'","t['cable:type'] = 'communication'"],
-            ["t.content && !(t.product)","t.product = t.content; delete t.content"],
-            ["t.construction && t.highway","t.highway = t.construction; t.condition = 'construction'; delete t.construction"],
-            ["t.construction && t.railway","t.railway = t.construction; t.condition = 'construction'; delete t.construction"],
-            ["t.control_tower && t.man_made == 'tower'","delete t.man_made"],
-            ["t.diplomatic && t.amenity == 'embassy'","delete t.amenity"],
-            ["t.highway == 'bus_stop'","t['transport:type'] = 'bus'"],
-            ["t.highway == 'crossing'","t['transport:type'] = 'road';a.F_CODE = 'AQ062'; delete t.highway"],
-            ["t.highway == 'give-way'","a.F_CODE = 'AQ062'"],
-            ["t.highway == 'mini_roundabout'","t.junction = 'roundabout'"],
-            ["t.highway == 'steps'","t.highway = 'footway'"],
-            ["t.highway == 'stop'","a.F_CODE = 'AQ062'"],
-            ["t.historic == 'castle' && t.building","delete t.building"],
-            ["t.historic == 'castle' && t.ruins == 'yes'","t.condition = 'destroyed'; delete t.ruins"],
-            ["t.landcover == 'snowfield' || t.landcover == 'ice-field'","a.F_CODE = 'BJ100'"],
-            ["t.landuse == 'farmland' && t.crop == 'fruit_tree'","t.landuse = 'orchard'"],
-            ["t.landuse == 'railway' && t['railway:yard'] == 'marshalling_yard'","a.F_CODE = 'AN060'"],
-            ["t.landuse == 'reservoir'","t.water = 'reservoir'; delete t.landuse"],
-            ["t.landuse == 'scrub'","t.natural = 'scrub'; delete t.landuse"],
-            ["t.launch_pad","delete t.launch_pad; t.aeroway='launchpad'"],
-            ["t.leisure == 'recreation_ground'","t.landuse = 'recreation_ground'; delete t.leisure"],
-            ["t.leisure == 'sports_centre'","t.facility = 'yes'; t.use = 'recreation'; delete t.leisure"],
-            ["t.leisure == 'stadium' && t.building","delete t.building"],
-            ["t.man_made && t.building == 'yes'","delete t.building"],
-            ["t.man_made == 'launch_pad'","delete t.man_made; t.aeroway='launchpad'"],
-            ["t.median == 'yes'","t.is_divided = 'yes'"],
-            ["t.natural == 'desert' && t.surface","t.desert_surface = t.surface; delete t.surface"],
-            ["t.natural == 'sinkhole'","a.F_CODE = 'BH145'; t['water:sink:type'] = 'sinkhole'; delete t.natural"],
-            ["t.natural == 'spring' && !(t['spring:type'])","t['spring:type'] = 'spring'"],
-            ["t.natural == 'wood'","t.landuse = 'forest'; delete t.natural"],
-            ["t.power == 'pole'","t['cable:type'] = 'power';t['tower:shape'] = 'pole'"],
-            ["t.power == 'tower'","t['cable:type'] = 'power'; t.pylon = 'yes'; delete t.power"],
-            ["t.power == 'line'","t['cable:type'] = 'power', t.cable = 'yes'; delete t.power"],
-            ["t.power == 'generator'","t.use = 'power_generation'; a.F_CODE = 'AL013'"],
-            ["t.rapids == 'yes'","t.waterway = 'rapids'; delete t.rapids"],
-            ["t.railway == 'station'","t.public_transport = 'station';  t['transport:type'] = 'railway'"],
-            ["t.railway == 'level_crossing'","t['transport:type'] = 'railway';t['transport:type:2'] = 'road'; a.F_CODE = 'AQ062'; delete t.railway"],
-            ["t.railway == 'crossing'","t['transport:type'] = 'railway'; a.F_CODE = 'AQ062'; delete t.railway"],
-            ["t.resource","t.raw_material = t.resource; delete t.resource"],
-            ["t.route == 'road' && !(t.highway)","t.highway = 'road'; delete t.route"],
-            // ["(t.shop || t.office) &&  !(t.building)","a.F_CODE = 'AL013'"],
-            ["t.social_facility == 'shelter'","t.social_facility = t['social_facility:for']; delete t.amenity; delete t['social_facility:for']"],
-            ["t['tower:type'] == 'minaret' && t.man_made == 'tower'","delete t.man_made"],
-            ["t.tunnel == 'building_passage'","t.tunnel = 'yes'"],
-            ["t.use == 'islamic_prayer_hall' && t.amenity == 'place_of_worship'","delete t.amenity"],
-            ["!(t.water) && t.natural == 'water'","t.water = 'lake'"],
-            ["t.wetland && t.natural == 'wetland'","delete t.natural"],
-            ["t.water == 'river'","t.waterway = 'river'"],
-            ["t.waterway == 'riverbank'","t.waterway = 'river'"]
-            ];
-
-            tds40.PreRules = translate.buildComplexRules(rulesList);
-        }
-
-        // Apply the rulesList
-        // translate.applyComplexRules(tags,attrs,tds40.PreRules);
-        // Pulling this out of translate
-        for (var i = 0, rLen = tds40.PreRules.length; i < rLen; i++)
-        {
-            if (tds40.PreRules[i][0](tags)) tds40.PreRules[i][1](tags,attrs);
-        }
-
-        // Fix up OSM 'walls' around facilities
-        if (tags.barrier == 'wall' && geometryType == 'Area')
-        {
-            attrs.F_CODE = 'AL010'; // Facility
-            delete tags.barrier; // Take away the walls...
-        }
-
-        // going out on a limb and processing OSM specific tags:
-        // - Building == a thing,
-        // - Amenity == The area around a thing
-        // Note: amenity=place_of_worship is a special case. It _should_ have an associated building tag
-        var facilityList = {'school':'850','university':'855','college':'857','hospital':'860'};
-
-        if (tags.amenity in facilityList)
-        {
-            if (geometryType == 'Area')
-            {
-                attrs.F_CODE = 'AL010'; // Facility
-
-                // If the user has also set a building tag, delete it
-                if (tags.building) delete tags.building;
-            }
-            else
-            {
-                // Make sure we don't turn point facilities into buildings
-                if (tags.facility !== 'yes')
-                {
-                    // Debug
-                    // print('Making a building: ' + tags.facility);
-                    attrs.F_CODE = 'AL013'; // Building
-                }
-            }
-
-            // If we don't have a Feature Function then assign one
-            if (! attrs.FFN)
-            {
-                attrs.FFN = facilityList[tags.amenity];
-                // Debug
-                // print('PreDropped: amenity = ' + tags.amenity);
-                delete tags.amenity;
-            }
-        }
-
-        // Adding a custom rule for malls to override the rules above
-        // All of the other "shops" are buildings
-        if (tags.shop == 'mall') attrs.F_CODE = 'AG030';
-
-        // Churches etc
-        if (tags.building && ! tags.amenity)
-        {
-            var how = [ 'church','chapel','cathedral','mosque','pagoda','shrine','temple',
-                        'synagogue','tabernacle','stupa']
-            if (how.indexOf(tags.building) > -1)
-            {
-                tags.amenity = 'place_of_worship';
-            }
-
-            var rc = [ 'mission','religious_community','seminary','convent','monastry',
-                       'noviciate','hermitage','retrest','marabout']
-            if (rc.indexOf(tags.building) > -1)
-            {
-                tags.use = 'religious_activities';
-            }
-        }
-
-        // Fix up water features from OSM
-        if (tags.natural == 'water' && !(tags.water))
-        {
-            if (geometryType =='Line')
-            {
-                tags.waterway = 'river';
-                attrs.F_CODE = 'BH140';
-            }
-            else
-            {
-                tags.water = 'lake';
-                attrs.F_CODE = 'BH082';
-            }
-        }
-
-        // Cutlines/Cleared Ways & Highways
-        // This might need a cleanup
-        if (tags.man_made == 'cutline' && tags.highway)
-        {
-            if (geometryType == 'Area')
-            {
-                // Keep the cutline/cleared way
-                attrs.F_CODE = 'EC040';
-
-                if (tags.memo)
-                {
-                    tags.memo = tags.memo + ';highway:' + tags.highway;
-                }
-                else
-                {
-                    tags.memo = 'highway:' + tags.highway;
-                }
-
-                delete tags.highway;
-            }
-            else
-            {
-                // Drop the cutline/cleared way
-                delete tags.man_made;
-
-                if (tags.memo)
-                {
-                    tags.memo = tags.memo + ';cleared_way:yes';
-                }
-                else
-                {
-                    tags.memo = 'cleared_way:yes';
-                }
-            }
-        }
-
-        // Fix up landuse & AL020
-        switch (tags.landuse)
-        {
-            case undefined: // Break early if no value
-                break;
-
-            case 'brownfield':
-                tags.landuse = 'built_up_area';
-                tags.condition = 'destroyed';
-                break
-
-            case 'commercial':
-            case 'retail':
-                tags.use = 'commercial';
-                tags.landuse = 'built_up_area';
-                break;
-
-            case 'construction':
-                tags.condition = 'construction';
-                tags.landuse = 'built_up_area';
-                break;
-
-            case 'farm':
-            case 'allotments':
-                tags.landuse = 'farmland';
-                break;
-
-            case 'farmyard': // NOTE: This is different to farm
-                tags.facility = 'yes';
-                tags.use = 'agriculture';
-                delete tags.landuse;
-                break;
-
-            case 'grass':
-                tags.natural = 'grassland';
-                tags['grassland:type'] = 'grassland';
-                delete tags.landuse;
-                break;
-
-            case 'industrial': // Deconflict with AA052 Hydrocarbons Field
-                switch (tags.industrial)
-                {
-                    case undefined: // Built up Area
-                        tags.use = 'industrial';
-                        tags.landuse = 'built_up_area';
-                        break;
-
-                    case 'oil':
-                        tags.product = 'petroleum';
-                        tags.industrial = 'hydrocarbons_field';
-                        delete tags.landuse;
-                        break;
-
-                    case 'gas':
-                        tags.product = 'gas';
-                        tags.industrial = 'hydrocarbons_field';
-                        delete tags.landuse;
-                        break;
-                }
-                break;
-
-            case 'military':
-                tags.military = 'installation';
-                delete tags.landuse;
-                break;
-
-            case 'meadow':
-                tags.natural = 'grassland';
-                tags['grassland:type'] = 'meadow';
-                delete tags.landuse;
-                break;
-
-            case 'residential':
-                tags.use = 'residential';
-                tags.landuse = 'built_up_area';
-                break;
-
-        } // End switch landuse
-
-        // Places, localities and regions
-        if (tags.place)
-        {
-            switch (tags.place)
-            {
-                case 'city':
-                case 'town':
-                case 'suburb':
-                case 'neighbourhood':
-                case 'quarter':
-                case 'village':
-                case 'hamlet':
-                case 'yes':  // We set this as a default when going to OSM
-                    attrs.F_CODE = 'AL020'; // Built Up Area
-                    delete tags.place;
-                    break;
-
-                case 'isolated_dwelling':
-                    attrs.F_CODE = 'AL105'; // Settlement
-                    delete tags.place;
-                    break;
-
-                case 'populated':
-                case 'state':
-                case 'county':
-                case 'region':
-                case 'locality':
-                case 'municipality':
-                case 'borough':
-                case 'unknown':
-                    attrs.F_CODE = 'ZD045'; // Annotated Location
-                    if (tags.memo)
-                    {
-                        tags.memo = tags.memo + ';annotation:' + tags.place;
-                    }
-                    else
-                    {
-                        tags.memo = 'annotation:' + tags.place;
-                    }
-                    delete tags.place;
-                    break;
-
-                case 'island':
-                case 'islet':
-                    // If we have a coastline around an Island, decide if we are going make an Island
-                    // or a Coastline
-                    if (tags.natural == 'coastline')
-                    {
-                        if (geometryType == 'Line')
-                        {
-                            attrs.F_CODE = 'BA010'; // Land/Water Boundary - Line
-                            delete tags.place;
-                        }
-                        else
-                        {
-                            // NOTE: Islands can be Points or Areas
-                            attrs.F_CODE = 'BA030'; // Island
-                            delete tags.natural;
-                        }
-                    }
-                    break;
-            } // End switch
-        }
-
-        // Bridges & Roads.  If we have an area or a line everything is fine
-        // If we have a point, we need to make sure that it becomes a bridge, not a road
-        if (tags.bridge && tags.bridge !== 'no' && geometryType =='Point') attrs.F_CODE = 'AQ040';
+    // Movable Bridges
+    if (tags.bridge == 'movable')
+    {
+      if (! tags['bridge:movable'])
+      {
+        	tags['bridge:movable'] = 'unknown';
+      }
+      tags.bridge = 'yes';
+      attrs.F_CODE = 'AQ040';
+    }
 
-        // Movable Bridges
-        if (tags.bridge == 'movable')
+    // Viaducts
+    if (tags.bridge == 'viaduct')
+    {
+      tags.bridge = 'yes';
+      tags.note = translate.appendValue(tags.note,'Viaduct',';');
+    }
+
+    // Fix road junctions
+    // TDS has junctions as points. If we can make the feature into a road, railway or bridge then we will
+    // If not, it should go to the o2s layer
+    if (tags.junction && geometryType !== 'Point')
+    {
+      if (tags.highway || tags.bridge || tags.railway)
+      {
+        delete tags.junction;
+      }
+    } // End AP020 not Point
+
+    // Cables
+    if (tags.man_made == 'submarine_cable')
+    {
+      delete tags.man_made;
+      tags.cable = 'yes';
+      tags.location = 'underwater';
+    }
+
+    // "service = parking_aisle" is actually the road between rows of car spaces.
+    // If this is a line: make it into a road - the default
+    // If this is an area: make it into a car park.
+    if (tags.service == 'parking_aisle' && geometryType == 'Area')
+    {
+      delete tags.highway;
+      delete tags.service;
+      attrs.F_CODE = 'AQ140'; // Vehicle lot / car park
+    }
+
+    // Now use the lookup table to find an FCODE. This is here to stop clashes with the
+    // standard one2one rules
+    if (!(attrs.F_CODE) && tds40.fcodeLookup)
+    {
+      for (var col in tags)
+      {
+        var value = tags[col];
+        if (col in tds40.fcodeLookup)
         {
-          if (! tags['bridge:movable'])
+          if (value in tds40.fcodeLookup[col])
           {
-        	tags['bridge:movable'] = 'unknown';
+            var row = tds40.fcodeLookup[col][value];
+            attrs.F_CODE = row[1];
+            // Debug
+            // print('FCODE: Got ' + attrs.F_CODE);
           }
-          tags.bridge = 'yes';
-          attrs.F_CODE = 'AQ040';
-        }
-
-        // Viaducts
-        if (tags.bridge == 'viaduct')
-        {
-          tags.bridge = 'yes';
-          tags.note = translate.appendValue(tags.note,'Viaduct',';');
-        }
-
-        // Fix road junctions
-        // TDS has junctions as points. If we can make the feature into a road, railway or bridge then we will
-        // If not, it should go to the o2s layer
-        if (tags.junction && geometryType !== 'Point')
-        {
-            if (tags.highway || tags.bridge || tags.railway)
-            {
-                delete tags.junction;
-            }
-        } // End AP020 not Point
-
-        // Cables
-        if (tags.man_made == 'submarine_cable')
-        {
-            delete tags.man_made;
-            tags.cable = 'yes';
-            tags.location = 'underwater';
-        }
-
-        // "service = parking_aisle" is actually the road between rows of car spaces.
-        // If this is a line: make it into a road - the default
-        // If this is an area: make it into a car park.
-        if (tags.service == 'parking_aisle' && geometryType == 'Area')
-        {
-            delete tags.highway;
-            delete tags.service;
-            attrs.F_CODE = 'AQ140'; // Vehicle lot / car park
-        }
-
-        // Now use the lookup table to find an FCODE. This is here to stop clashes with the
-        // standard one2one rules
-        if (!(attrs.F_CODE) && tds40.fcodeLookup)
-        {
-            for (var col in tags)
-            {
-                var value = tags[col];
-                if (col in tds40.fcodeLookup)
-                {
-                    if (value in tds40.fcodeLookup[col])
-                    {
-                        var row = tds40.fcodeLookup[col][value];
-                        attrs.F_CODE = row[1];
-                        // Debug
-                        // print('FCODE: Got ' + attrs.F_CODE);
-                    }
-                }
-            }
-        } // End find F_CODE
-
-        // Some tags imply that they are buildings but don't actually say so
-        // Most of these are taken from raw OSM and the OSM Wiki
-        // Not sure if the list of amenities that ARE buildings is shorter than the list of ones that
-        // are not buildings
-        // Taking "place_of_worship" out of this and making it a building
-        var notBuildingList = [
-            'artwork','atm','bbq','bench','bicycle_parking','bicycle_rental','biergarten','boat_sharing','car_sharing',
-            'charging_station','clock','compressed_air','dog_bin','dog_waste_bin','drinking_water','emergency_phone',
-            'ferry_terminal','fire_hydrant','fountain','game_feeding','grass_strip','grit_bin','hunting_stand','hydrant',
-            'life_ring','loading_dock','nameplate','park','parking','parking_entrance','parking_space','picnic_table',
-            'post_box','recycling','street_light','swimming_pool','taxi','trailer_park','tricycle_station','vending_machine',
-            'waste_basket','waste_disposal','water','water_point','watering_place','yes',
-            'fuel' // NOTE: Fuel goes to a different F_CODE
-            ]; // End notBuildingList
-
-        if (!(attrs.F_CODE) && !(tags.facility) && tags.amenity && !(tags.building) && (notBuildingList.indexOf(tags.amenity) == -1)) attrs.F_CODE = 'AL013';
-
-        // If we still don't have an FCODE, try looking for individual elements
-        if (!attrs.F_CODE)
-        {
-            var fcodeMap = {
-                'highway':'AP030','railway':'AN010','building':'AL013','ford':'BH070',
-                'waterway':'BH140','bridge':'AQ040','railway:in_road':'AN010',
-                'barrier':'AP040','tourism':'AL013','junction':'AP020','mine:access':'AA010',
-                'tomb':'AL036','shop':'AL015','office':'AL015'
-                           };
-
-            for (var i in fcodeMap)
-            {
-                if (i in tags)
-                {
-                    attrs.F_CODE = fcodeMap[i];
-                    break;
-                }
-            }
-        }
-
-        // Sort out PYM vs ZI032_PYM vs MCC vs VCM - Ugly
-        var pymList = [ 'AL110','AL241','AQ055','AQ110','AT042'];
-
-        var vcmList = [ 'AA040','AC020','AD010','AD025','AD030','AD041','AD050','AF010',
-                        'AF020','AF021','AF030','AF040','AF070','AH055','AJ050','AJ051',
-                        'AJ080','AJ085','AL010','AL013','AL019','AL080','AM011','AM020',
-                        'AM030','AM070','AN076','AQ040','AQ045','AQ060','AQ116','BC050',
-                        'BD115','BI010','BI050','GB230' ];
-
-        if (tags.material)
-        {
-            if (pymList.indexOf(attrs.F_CODE) !== -1)
-            {
-                tags['tower:material'] = tags.material;
-                delete tags.material;
-            }
-            else if (vcmList.indexOf(attrs.F_CODE) !== -1)
-            {
-                tags['material:vertical'] = tags.material;
-                delete tags.material;
-            }
         }
+      }
+    } // End find F_CODE
+
+    // Some tags imply that they are buildings but don't actually say so
+    // Most of these are taken from raw OSM and the OSM Wiki
+    // Not sure if the list of amenities that ARE buildings is shorter than the list of ones that
+    // are not buildings
+    // Taking "place_of_worship" out of this and making it a building
+    var notBuildingList = [
+      'artwork','atm','bbq','bench','bicycle_parking','bicycle_rental','biergarten','boat_sharing','car_sharing',
+      'charging_station','clock','compressed_air','dog_bin','dog_waste_bin','drinking_water','emergency_phone',
+      'ferry_terminal','fire_hydrant','fountain','game_feeding','grass_strip','grit_bin','hunting_stand','hydrant',
+      'life_ring','loading_dock','nameplate','park','parking','parking_entrance','parking_space','picnic_table',
+      'post_box','recycling','street_light','swimming_pool','taxi','trailer_park','tricycle_station','vending_machine',
+      'waste_basket','waste_disposal','water','water_point','watering_place','yes',
+      'fuel' // NOTE: Fuel goes to a different F_CODE
+    ]; // End notBuildingList
+
+    if (!(attrs.F_CODE) && !(tags.facility) && tags.amenity && !(tags.building) && (notBuildingList.indexOf(tags.amenity) == -1)) attrs.F_CODE = 'AL013';
+
+    // If we still don't have an FCODE, try looking for individual elements
+    if (!attrs.F_CODE)
+    {
+      var fcodeMap = {
+        'highway':'AP030','railway':'AN010','building':'AL013','ford':'BH070',
+        'waterway':'BH140','bridge':'AQ040','railway:in_road':'AN010',
+        'barrier':'AP040','tourism':'AL013','junction':'AP020','mine:access':'AA010',
+        'tomb':'AL036','shop':'AL015','office':'AL015'
+      };
 
-       // Protected areas have two attributes that need sorting out
-       if (tags.protection_object == 'habitat' || tags.protection_object == 'breeding_ground')
-       {
-           if (tags.protect_class) delete tags.protect_class;
-       }
-
-
-       // Now set the relative levels and transportation types for various features
-       if (tags.highway || tags.railway)
-       {
-           if (tags.bridge && tags.bridge !== 'no')
-           {
-               tds40.fixTransType(tags,geometryType);
-               tags.location = 'surface';
-               tags.layer = '1';
-               tags.on_bridge = 'yes';
-           }
-
-           if (tags.tunnel && tags.tunnel !== 'no')
-           {
-               tds40.fixTransType(tags,geometryType);
-               // tags.layer = '-1';
-               tags.in_tunnel = 'yes';
-           }
-
-           if (tags.embankment && tags.embankment !== 'no')
-           {
-               tds40.fixTransType(tags,geometryType);
-               tags.layer = '1';
-           }
-
-           if (tags.cutting && tags.cutting !== 'no')
-           {
-               tds40.fixTransType(tags,geometryType);
-               tags.layer = '-1';
-           }
-
-           if (tags.ford && tags.ford !== 'no')
-           {
-               tds40.fixTransType(tags,geometryType);
-               tags.location = 'on_waterbody_bottom';
-           }
-
-       } // End if highway || railway
-
-       // Tag changed
-       if (tags.vertical_obstruction_identifier)
-       {
-           tags['aeroway:obstruction'] = tags.vertical_obstruction_identifier;
-           delete tags.vertical_obstruction_identifier;
-       }
-
-       // Railway loading things
-       if (tags.railway == 'loading')
-       {
-           if (tags.facility == 'gantry_crane')
-           {
-               delete tags.railway;
-               delete tags.facility;
-               attrs.F_CODE = 'AF040'; // Crane
-               tags['crane:type'] = 'bridge';
-           }
-
-           if (tags.facility == 'container_terminal')
-           {
-               delete tags.railway;
-               delete tags.facility;
-               attrs.F_CODE = 'AL010'; // Facility
-               attrs.FFN = '480'; // Transportation
-           }
-       } // End loading
-
-        switch (tags.man_made)
+      for (var i in fcodeMap)
+      {
+        if (i in tags)
         {
-            case undefined: // Break early if no value
-                break;
-
-            case 'reservoir_covered':
-                delete tags.man_made;
-                attrs.F_CODE = 'AM070'; // Storage Tank
-                tags.product = 'water';
-                break;
-
-            case 'gasometer':
-                delete tags.man_made;
-                attrs.F_CODE = 'AM070'; // Storage Tank
-                tags.product = 'gas';
-                break;
+          attrs.F_CODE = fcodeMap[i];
+          break;
         }
+      }
+    }
 
-        // Stop some Religion tags from stomping on Denomination tags
-        if (tags.religion && tags.denomination)
-        {
-            if (tags.religion == 'christian' || tags.religion == 'muslim')
-            {
-                switch (tags.denomination)
-                {
-                    case 'roman_catholic':
-                    case 'orthodox':
-                    case 'protestant':
-                    case 'chaldean_catholic':
-                    case 'nestorian': // Not sure about this
-                    case 'shia':
-                    case 'sunni':
-                        delete tags.religion;
-                        break;
-                } // End switch
-            }
-        } // End if religion & denomination
-
-        // Names. Sometimes we don't have a name but we do have language ones
-        if (!tags.name) translate.swapName(tags);
+    // Sort out PYM vs ZI032_PYM vs MCC vs VCM - Ugly
+    var pymList = [ 'AL110','AL241','AQ055','AQ110','AT042'];
 
-    }, // End applyToTdsPreProcessing
+    var vcmList = [ 'AA040','AC020','AD010','AD025','AD030','AD041','AD050','AF010',
+      'AF020','AF021','AF030','AF040','AF070','AH055','AJ050','AJ051',
+      'AJ080','AJ085','AL010','AL013','AL019','AL080','AM011','AM020',
+      'AM030','AM070','AN076','AQ040','AQ045','AQ060','AQ116','BC050',
+      'BD115','BI010','BI050','GB230' ];
 
-// #####################################################################################################
-
-    applyToTdsPostProcessing : function (tags, attrs, geometryType, notUsedTags)
+    if (tags.material)
+    {
+      if (pymList.indexOf(attrs.F_CODE) !== -1)
+      {
+        tags['tower:material'] = tags.material;
+        delete tags.material;
+      }
+      else if (vcmList.indexOf(attrs.F_CODE) !== -1)
+      {
+        tags['material:vertical'] = tags.material;
+        delete tags.material;
+      }
+    }
+
+    // Protected areas have two attributes that need sorting out
+    if (tags.protection_object == 'habitat' || tags.protection_object == 'breeding_ground')
     {
-        // Shoreline Construction (BB081) covers a lot of features
-        if (attrs.PWC) attrs.F_CODE = 'BB081';
+      if (tags.protect_class) delete tags.protect_class;
+    }
 
-        // Inland Water Body (BH082) also covers a lot of features
-        if (attrs.IWT && !(attrs.F_CODE)) attrs.F_CODE = 'BH082';
 
-        // The follwing bit of ugly code is to account for the specs haveing two different attributes
-        // with similar names and roughly the same attributes. Bleah!
-        if (tds40.rules.swapListOut[attrs.F_CODE])
-        {
-            for (var i in tds40.rules.swapListOut[attrs.F_CODE])
-            {
-                if (i in attrs)
-                {
-                    attrs[tds40.rules.swapListOut[attrs.F_CODE][i]] = attrs[i];
-                    delete attrs[i]
-                }
-            }
-        }
+    // Now set the relative levels and transportation types for various features
+    if (tags.highway || tags.railway)
+    {
+      if (tags.bridge && tags.bridge !== 'no')
+      {
+        tds40.fixTransType(tags,geometryType);
+        tags.location = 'surface';
+        tags.layer = '1';
+        tags.on_bridge = 'yes';
+      }
+
+      if (tags.tunnel && tags.tunnel !== 'no')
+      {
+        tds40.fixTransType(tags,geometryType);
+        // tags.layer = '-1';
+        tags.in_tunnel = 'yes';
+      }
+
+      if (tags.embankment && tags.embankment !== 'no')
+      {
+        tds40.fixTransType(tags,geometryType);
+        tags.layer = '1';
+      }
+
+      if (tags.cutting && tags.cutting !== 'no')
+      {
+        tds40.fixTransType(tags,geometryType);
+        tags.layer = '-1';
+      }
+
+      if (tags.ford && tags.ford !== 'no')
+      {
+        tds40.fixTransType(tags,geometryType);
+        tags.location = 'on_waterbody_bottom';
+      }
+
+    } // End if highway || railway
+
+    // Tag changed
+    if (tags.vertical_obstruction_identifier)
+    {
+      tags['aeroway:obstruction'] = tags.vertical_obstruction_identifier;
+      delete tags.vertical_obstruction_identifier;
+    }
 
-        // Sort out the UUID
-        if (attrs.UFI)
-        {
-            var str = attrs['UFI'].split(';');
-            attrs.UFI = str[0].replace('{','').replace('}','');
-        }
-        else if (tags['hoot:id'])
-        {
-            attrs.UFI = 'raw_id:' + tags['hoot:id'];
-        }
-        else
+    // Railway loading things
+    if (tags.railway == 'loading')
+    {
+      if (tags.facility == 'gantry_crane')
+      {
+        delete tags.railway;
+        delete tags.facility;
+        attrs.F_CODE = 'AF040'; // Crane
+        tags['crane:type'] = 'bridge';
+      }
+
+      if (tags.facility == 'container_terminal')
+      {
+        delete tags.railway;
+        delete tags.facility;
+        attrs.F_CODE = 'AL010'; // Facility
+        attrs.FFN = '480'; // Transportation
+      }
+    } // End loading
+
+    switch (tags.man_made)
+    {
+    case undefined: // Break early if no value
+      break;
+
+    case 'reservoir_covered':
+      delete tags.man_made;
+      attrs.F_CODE = 'AM070'; // Storage Tank
+      tags.product = 'water';
+      break;
+
+    case 'gasometer':
+      delete tags.man_made;
+      attrs.F_CODE = 'AM070'; // Storage Tank
+      tags.product = 'gas';
+      break;
+    }
+
+    // Stop some Religion tags from stomping on Denomination tags
+    if (tags.religion && tags.denomination)
+    {
+      if (tags.religion == 'christian' || tags.religion == 'muslim')
+      {
+        switch (tags.denomination)
+        {
+        case 'roman_catholic':
+        case 'orthodox':
+        case 'protestant':
+        case 'chaldean_catholic':
+        case 'nestorian': // Not sure about this
+        case 'shia':
+        case 'sunni':
+          delete tags.religion;
+          break;
+        } // End switch
+      }
+    } // End if religion & denomination
+
+    // Names. Sometimes we don't have a name but we do have language ones
+    if (!tags.name) translate.swapName(tags);
+
+  }, // End applyToTdsPreProcessing
+
+  // #####################################################################################################
+
+  applyToTdsPostProcessing : function (tags, attrs, geometryType, notUsedTags)
+  {
+    // Shoreline Construction (BB081) covers a lot of features
+    if (attrs.PWC) attrs.F_CODE = 'BB081';
+
+    // Inland Water Body (BH082) also covers a lot of features
+    if (attrs.IWT && !(attrs.F_CODE)) attrs.F_CODE = 'BH082';
+
+    // The follwing bit of ugly code is to account for the specs haveing two different attributes
+    // with similar names and roughly the same attributes. Bleah!
+    if (tds40.rules.swapListOut[attrs.F_CODE])
+    {
+      for (var i in tds40.rules.swapListOut[attrs.F_CODE])
+      {
+        if (i in attrs)
         {
-            if (tds40.configOut.OgrAddUuid == 'true') attrs.UFI = createUuid().replace('{','').replace('}','');
+          attrs[tds40.rules.swapListOut[attrs.F_CODE][i]] = attrs[i];
+          delete attrs[i];
         }
+      }
+    }
 
-        // Add Weather Restrictions to transportation features
-        if (['AP010','AP030','AP050'].indexOf(attrs.FCODE > -1) && !attrs.ZI016_WTC )
-        {
-            switch (tags.highway)
-            {
-                case 'motorway':
-                case 'motorway_link':
-                case 'trunk':
-                case 'trunk_link':
-                case 'primary':
-                case 'primary_link':
-                case 'secondary':
-                case 'secondary_link':
-                case 'tertiary':
-                case 'tertiary_link':
-                case 'residential':
-                case 'unclassified':
-                    attrs.ZI016_WTC = '1'; // All weather
-                    break;
-
-                case 'track':
-                    attrs.ZI016_WTC = '4'; // Limited All-weather
-                    break;
-
-                case 'path':
-                    attrs.ZI016_WTC = '2'; // Fair Weather
-                    break;
-            }
-
-            // Use the road surface to possibly override the classification
-            // We are assumeing that unpaved roads are Fair Weather only
-            switch (attrs.ZI016_ROC)
-            {
-                case undefined: // Break early if no value
-                    break;
-
-                case '1': // Unimproved
-                    attrs.ZI016_WTC = '2'; // Fair Weather
-                    break;
-
-                case '2': // Stabilized Earth
-                case '4': // Gravel
-                    attrs.ZI016_WTC = '4'; // Limited All-weather
-                    break;
-
-                case '17': // Ice
-                case '18': // Snow
-                    attrs.ZI016_WTC = '3'; // Winter Only
-                    break;
-
-                case '999': // Other
-                    attrs.ZI016_WTC = '-999999'; // No Information
-                    break;
-
-                default:
-                    attrs.ZI016_WTC = '1' // All Weather
-            }
-        }
+    // Sort out the UUID
+    if (attrs.UFI)
+    {
+      var str = attrs['UFI'].split(';');
+      attrs.UFI = str[0].replace('{','').replace('}','');
+    }
+    else if (tags['hoot:id'])
+    {
+      attrs.UFI = 'raw_id:' + tags['hoot:id'];
+    }
+    else
+    {
+      if (tds40.configOut.OgrAddUuid == 'true') attrs.UFI = createUuid().replace('{','').replace('}','');
+    }
 
-        // Rules for specific F_CODES
-        switch (attrs.F_CODE)
+    // Add Weather Restrictions to transportation features
+    if (['AP010','AP030','AP050'].indexOf(attrs.FCODE > -1) && !attrs.ZI016_WTC )
+    {
+      switch (tags.highway)
+      {
+      case 'motorway':
+      case 'motorway_link':
+      case 'trunk':
+      case 'trunk_link':
+      case 'primary':
+      case 'primary_link':
+      case 'secondary':
+      case 'secondary_link':
+      case 'tertiary':
+      case 'tertiary_link':
+      case 'residential':
+      case 'unclassified':
+        attrs.ZI016_WTC = '1'; // All weather
+        break;
+
+      case 'track':
+        attrs.ZI016_WTC = '4'; // Limited All-weather
+        break;
+
+      case 'path':
+        attrs.ZI016_WTC = '2'; // Fair Weather
+        break;
+      }
+
+      // Use the road surface to possibly override the classification
+      // We are assumeing that unpaved roads are Fair Weather only
+      switch (attrs.ZI016_ROC)
+      {
+      case undefined: // Break early if no value
+        break;
+
+      case '1': // Unimproved
+        attrs.ZI016_WTC = '2'; // Fair Weather
+        break;
+
+      case '2': // Stabilized Earth
+      case '4': // Gravel
+        attrs.ZI016_WTC = '4'; // Limited All-weather
+        break;
+
+      case '17': // Ice
+      case '18': // Snow
+        attrs.ZI016_WTC = '3'; // Winter Only
+        break;
+
+      case '999': // Other
+        attrs.ZI016_WTC = '-999999'; // No Information
+        break;
+
+      default:
+        attrs.ZI016_WTC = '1'; // All Weather
+      }
+    }
+
+    // Rules for specific F_CODES
+    switch (attrs.F_CODE)
+    {
+    case 'AP030': // Custom Road rules
+      // - Fix the "highway=" stuff that cant be done in the one2one rules
+      // If we haven't sorted out the road type/class, have a try with the
+      // "highway" tag. If that doesn't work, we end up with default values
+      // These are pretty vague classifications
+      if (!attrs.TYP && !attrs.RTN_ROI)
+      {
+        switch (tags.highway)
         {
-            case 'AP030': // Custom Road rules
-                // - Fix the "highway=" stuff that cant be done in the one2one rules
-                // If we haven't sorted out the road type/class, have a try with the
-                // "highway" tag. If that doesn't work, we end up with default values
-                // These are pretty vague classifications
-                if (!attrs.TYP && !attrs.RTN_ROI)
-                {
-                    switch (tags.highway)
-                    {
-                        case 'motorway':
-                        case 'motorway_link':
-                            attrs.RTN_ROI = '2'; // National Motorway
-                            attrs.TYP = '41'; // motorway
-                            break;
-
-                        case 'trunk':
-                        case 'trunk_link':
-                            attrs.RTN_ROI = '3'; // National/Primary
-                            attrs.TYP = '47'; // Limited Access Motorway
-                            break;
-
-                        case 'primary':
-                        case 'primary_link':
-                            attrs.RTN_ROI = '4'; // Secondary
-                            attrs.TYP = '1'; // road
-                            break;
-
-                        case 'secondary':
-                        case 'secondary_link':
-                        case 'tertiary':
-                        case 'tertiary_link':
-                            attrs.RTN_ROI = '5'; // Local
-                            attrs.TYP = '1'; // road
-                            break;
-
-                        case 'residential':
-                        case 'unclassified':
-                        case 'service':
-                            attrs.RTN_ROI = '5'; // Local
-                            attrs.TYP = '33'; // street
-                            break;
-
-                        case 'road':
-                            attrs.RTN_ROI = '-999999'; // No Information
-                            attrs.TYP = '-999999'; // No Information
-                    } // End tags.highway switch}
-                } // End if TYP/RTN_ROISL
-                break;
-
-            case 'AH055': // Fortified Building
-                if (attrs.FZR && !(attrs.FFN)) attrs.FFN = '835'; // Fortification type -> Defence Activities
-                break;
-
-            case 'AL020': // AL020 Built-Up-Areas have ZI005_FNA1 instead of ZI005_FNA. Why???
-                if (attrs.ZI005_FNA)
-                {
-                    attrs.ZI005_FNA1 = attrs.ZI005_FNA;
-                    delete attrs.ZI005_FNA;
-                }
-                break;
-
-            case 'AP010': // Clean up Cart Track attributes
-                if (attrs.TRS && (['3','4','6','11','21','22','999'].indexOf(attrs.TRS) == -1))
-                {
-                    var othVal = '(TRS:' + attrs.TRS + ')';
-                    attrs.OTH = translate.appendValue(attrs.OTH,othVal,' ');
-                    attrs.TRS = '999';
-                }
-                break;
-
-            case 'ZI040': // Spatial Metadata Entity Collection
-                //Map alternate source date tags to ZI001_SSD in order of precedence
-                //default is 'source:datetime'
-                if (! attrs.ZI001_SSD)
-                    attrs.ZI001_SSD = tags['source:imagery:datetime']
+        case 'motorway':
+        case 'motorway_link':
+          attrs.RTN_ROI = '2'; // National Motorway
+          attrs.TYP = '41'; // motorway
+          break;
+
+        case 'trunk':
+        case 'trunk_link':
+          attrs.RTN_ROI = '3'; // National/Primary
+          attrs.TYP = '47'; // Limited Access Motorway
+          break;
+
+        case 'primary':
+        case 'primary_link':
+          attrs.RTN_ROI = '4'; // Secondary
+          attrs.TYP = '1'; // road
+          break;
+
+        case 'secondary':
+        case 'secondary_link':
+        case 'tertiary':
+        case 'tertiary_link':
+          attrs.RTN_ROI = '5'; // Local
+          attrs.TYP = '1'; // road
+          break;
+
+        case 'residential':
+        case 'unclassified':
+        case 'service':
+          attrs.RTN_ROI = '5'; // Local
+          attrs.TYP = '33'; // street
+          break;
+
+        case 'road':
+          attrs.RTN_ROI = '-999999'; // No Information
+          attrs.TYP = '-999999'; // No Information
+        } // End tags.highway switch}
+      } // End if TYP/RTN_ROISL
+      break;
+
+    case 'AH055': // Fortified Building
+      if (attrs.FZR && !(attrs.FFN)) attrs.FFN = '835'; // Fortification type -> Defence Activities
+      break;
+
+    case 'AL020': // AL020 Built-Up-Areas have ZI005_FNA1 instead of ZI005_FNA. Why???
+      if (attrs.ZI005_FNA)
+      {
+        attrs.ZI005_FNA1 = attrs.ZI005_FNA;
+        delete attrs.ZI005_FNA;
+      }
+      break;
+
+    case 'AP010': // Clean up Cart Track attributes
+      if (attrs.TRS && (['3','4','6','11','21','22','999'].indexOf(attrs.TRS) == -1))
+      {
+        var othVal = '(TRS:' + attrs.TRS + ')';
+        attrs.OTH = translate.appendValue(attrs.OTH,othVal,' ');
+        attrs.TRS = '999';
+      }
+      break;
+
+    case 'ZI040': // Spatial Metadata Entity Collection
+      //Map alternate source date tags to ZI001_SSD in order of precedence
+      //default is 'source:datetime'
+      if (! attrs.ZI001_SSD)
+        attrs.ZI001_SSD = tags['source:imagery:datetime']
                         || tags['source:date']
                         || tags['source:geometry:date']
                         || '';
 
-                //Map alternate source tags to ZI001_SSN in order of precedence
-                //default is 'source'
-                if (! attrs.ZI001_SSN)
-                    attrs.ZI001_SSN = tags['source:imagery']
+      //Map alternate source tags to ZI001_SSN in order of precedence
+      //default is 'source'
+      if (! attrs.ZI001_SSN)
+        attrs.ZI001_SSN = tags['source:imagery']
                         || tags['source:description']
                         || '';
-                break;
+      break;
 
-            case 'AH025': // Engineered Earthwork
-                if (! attrs.EET) attrs.EET = '3';
-                break;
+    case 'AH025': // Engineered Earthwork
+      if (! attrs.EET) attrs.EET = '3';
+      break;
 
-            case 'AK030': // Amusement Parks
-                if (!attrs.FFN) attrs.FFN = '921'; // Recreation
-                break;
+    case 'AK030': // Amusement Parks
+      if (!attrs.FFN) attrs.FFN = '921'; // Recreation
+      break;
 
-        } // End switch F_CODE
+    } // End switch F_CODE
 
-        // TODO: Need to sort out Sinkholes. If FCODE = BH145 and no WST, fix
+    // TODO: Need to sort out Sinkholes. If FCODE = BH145 and no WST, fix
 
-        // RLE vs LOC: Need to deconflict this for various features
-        // locList: list of features that can be "Above Surface". Other features use RLE (Relitive Level) instead
-        // var locList = ['AT005','AQ113','BH065','BH110'];
-//         if (attrs.LOC == '45' && (locList.indexOf(attrs.TRS) == -1))
-        if (attrs.LOC == '45' && (['AT005','AQ113','BH065','BH110'].indexOf(attrs.TRS) == -1))
-        {
-            attrs.RLE = '2'; // Raised above surface
-            attrs.LOC = '44'; // On Surface
-        }
+    // RLE vs LOC: Need to deconflict this for various features
+    // locList: list of features that can be "Above Surface". Other features use RLE (Relitive Level) instead
+    // var locList = ['AT005','AQ113','BH065','BH110'];
+    //         if (attrs.LOC == '45' && (locList.indexOf(attrs.TRS) == -1))
+    if (attrs.LOC == '45' && (['AT005','AQ113','BH065','BH110'].indexOf(attrs.TRS) == -1))
+    {
+      attrs.RLE = '2'; // Raised above surface
+      attrs.LOC = '44'; // On Surface
+    }
 
-        // Fix HGT and LMC to keep GAIT happy
-        // If things have a height greater than 46m, tags them as being a "Navigation Landmark"
-        if (attrs.HGT > 46 && !(attrs.LMC)) attrs.LMC = '1001';
+    // Fix HGT and LMC to keep GAIT happy
+    // If things have a height greater than 46m, tags them as being a "Navigation Landmark"
+    if (attrs.HGT > 46 && !(attrs.LMC)) attrs.LMC = '1001';
 
-        // Alt_Name:  AL020 Built Up Area is the _ONLY_ feature in TDS that has a secondary name
-        if (attrs.ZI005_FNA2 && attrs.F_CODE !== 'AL020')
-        {
-            // We were going to push the Alt Name onto the end of the standard name field - ZI005_FNA
-            // but this causes problems so until the customer gives us more direction, we are going to drop it
-            // attrs.ZI005_FNA = translate.appendValue(attrs.ZI005_FNA,attrs.ZI005_FNA2,';');
-            delete attrs.ZI005_FNA2;
-        }
+    // Alt_Name:  AL020 Built Up Area is the _ONLY_ feature in TDS that has a secondary name
+    if (attrs.ZI005_FNA2 && attrs.F_CODE !== 'AL020')
+    {
+      // We were going to push the Alt Name onto the end of the standard name field - ZI005_FNA
+      // but this causes problems so until the customer gives us more direction, we are going to drop it
+      // attrs.ZI005_FNA = translate.appendValue(attrs.ZI005_FNA,attrs.ZI005_FNA2,';');
+      delete attrs.ZI005_FNA2;
+    }
+
+    // Wetlands
+    // Normally, these go to Marsh
+    switch(tags.wetland)
+    {
+    case undefined: // Break early if no value
+      break;
 
-        // Wetlands
-        // Normally, these go to Marsh
-        switch(tags.wetland)
-        {
-            case undefined: // Break early if no value
-                break;
+    case 'mangrove':
+      attrs.F_CODE = 'ED020'; // Swamp
+      attrs.VSP = '19'; // Mangrove
+      break;
+    } // End Wetlands
 
-            case 'mangrove':
-                attrs.F_CODE = 'ED020'; // Swamp
-                attrs.VSP = '19'; // Mangrove
-                break;
-        } // End Wetlands
+  }, // End applyToTdsPostProcessing
 
-    }, // End applyToTdsPostProcessing
+  // #####################################################################################################
 
-// #####################################################################################################
+  // ##### End of the xxToTdsxx Block #####
 
-    // ##### End of the xxToTdsxx Block #####
+  // toOsm - Translate Attrs to Tags
+  // This is the main routine to convert _TO_ OSM
+  toOsm : function(attrs, layerName, geometryType)
+  {
+    tags = {};  // The final output Tag list
 
-    // toOsm - Translate Attrs to Tags
-    // This is the main routine to convert _TO_ OSM
-    toOsm : function(attrs, layerName, geometryType)
+    // Setup config variables. We could do this in initialize() but some things don't call it :-(
+    // Doing this so we don't have to keep calling into Hoot core
+    if (tds40.configIn == undefined)
     {
-        tags = {};  // The final output Tag list
+      tds40.configIn = {};
+      tds40.configIn.OgrDebugAddfcode = config.getOgrDebugAddfcode();
+      tds40.configIn.OgrDebugDumptags = config.getOgrDebugDumptags();
+      tds40.configIn.OgrAddUuid = config.getOgrAddUuid();
 
-        // Setup config variables. We could do this in initialize() but some things don't call it :-(
-        // Doing this so we don't have to keep calling into Hoot core
-        if (tds40.configIn == undefined)
-        {
-            tds40.configIn = {};
-            tds40.configIn.OgrDebugAddfcode = config.getOgrDebugAddfcode();
-            tds40.configIn.OgrDebugDumptags = config.getOgrDebugDumptags();
-            tds40.configIn.OgrAddUuid = config.getOgrAddUuid();
+      // Get any changes
+      tds40.toChange = hoot.Settings.get('schema.translation.override');
+    }
 
-            // Get any changes
-            tds40.toChange = hoot.Settings.get("schema.translation.override");
-        }
+    // Debug:
+    if (tds40.configIn.OgrDebugDumptags == 'true') translate.debugOutput(attrs,layerName,geometryType,'','In attrs: ');
 
-        // Debug:
-        if (tds40.configIn.OgrDebugDumptags == 'true') translate.debugOutput(attrs,layerName,geometryType,'','In attrs: ');
+    // See if we have an o2s_X layer and try to unpack it
+    if (layerName.indexOf('o2s_') > -1)
+    {
+      tags = translate.parseO2S(attrs);
 
-        // See if we have an o2s_X layer and try to unpack it
-        if (layerName.indexOf('o2s_') > -1)
-        {
-            tags = translate.parseO2S(attrs);
+      // Add some metadata
+      if (! tags.uuid)
+      {
+        if (tds40.configIn.OgrAddUuid == 'true') tags.uuid = createUuid();
+      }
 
-            // Add some metadata
-            if (! tags.uuid)
-            {
-                if (tds40.configIn.OgrAddUuid == 'true') tags.uuid = createUuid();
-            }
+      if (! tags.source) tags.source = 'tdsv40:' + layerName.toLowerCase();
 
-            if (! tags.source) tags.source = 'tdsv40:' + layerName.toLowerCase();
+      // Debug:
+      if (tds40.configIn.OgrDebugDumptags == 'true')
+      {
+        translate.debugOutput(tags,layerName,geometryType,'','Out tags: ');
+        print('');
+      }
 
-            // Debug:
-            if (tds40.configIn.OgrDebugDumptags == 'true')
-            {
-                translate.debugOutput(tags,layerName,geometryType,'','Out tags: ');
-                print('');
-            }
+      return tags;
+    } // End layername = o2s_X
 
-            return tags;
-        } // End layername = o2s_X
+    // Set up the fcode translation rules. We need this due to clashes between the one2one and
+    // the fcode one2one rules
+    if (tds40.fcodeLookup == undefined)
+    {
+      // Add the FCODE rules for Import
+      fcodeCommon.one2one.push.apply(fcodeCommon.one2one,tds40.rules.fcodeOne2oneIn);
 
-        // Set up the fcode translation rules. We need this due to clashes between the one2one and
-        // the fcode one2one rules
-        if (tds40.fcodeLookup == undefined)
-        {
-            // Add the FCODE rules for Import
-            fcodeCommon.one2one.push.apply(fcodeCommon.one2one,tds40.rules.fcodeOne2oneIn);
+      tds40.fcodeLookup = translate.createLookup(fcodeCommon.one2one);
+      // Debug
+      // translate.dumpOne2OneLookup(tds40.fcodeLookup);
+    }
 
-            tds40.fcodeLookup = translate.createLookup(fcodeCommon.one2one);
-            // Debug
-            // translate.dumpOne2OneLookup(tds40.fcodeLookup);
-        }
+    if (tds40.lookup == undefined)
+    {
+      // Setup lookup tables to make translation easier. I'm assumeing that since this is not set, the
+      // other tables are not set either
 
-        if (tds40.lookup == undefined)
-        {
-            // Setup lookup tables to make translation easier. I'm assumeing that since this is not set, the
-            // other tables are not set either
+      // Support TDS v30 and other Import Only attributes
+      tds40.rules.one2one.push.apply(tds40.rules.one2one,tds40.rules.one2oneIn);
 
-            // Support TDS v30 and other Import Only attributes
-            tds40.rules.one2one.push.apply(tds40.rules.one2one,tds40.rules.one2oneIn);
+      tds40.lookup = translate.createLookup(tds40.rules.one2one);
+    }
 
-            tds40.lookup = translate.createLookup(tds40.rules.one2one);
-        }
+    // A little cleaning before we try to untangle stuff
+    delete attrs.SHAPE_Length;
+    delete attrs.SHAPE_Area;
 
-        // A little cleaning before we try to untangle stuff
-        delete attrs.SHAPE_Length;
-        delete attrs.SHAPE_Area;
+    // Untangle TDS attributes & OSM tags
+    // NOTE: This could get wrapped with an ENV variable so it only gets called during import
+    tds40.untangleAttributes(attrs, tags);
 
-        // Untangle TDS attributes & OSM tags
-        // NOTE: This could get wrapped with an ENV variable so it only gets called during import
-        tds40.untangleAttributes(attrs, tags);
+    // Debug:
+    if (tds40.configIn.OgrDebugDumptags == 'true')
+    {
+      translate.debugOutput(attrs,layerName,geometryType,'','Untangle attrs: ');
+      translate.debugOutput(tags,layerName,geometryType,'','Untangle tags: ');
+    }
 
-        // Debug:
-        if (tds40.configIn.OgrDebugDumptags == 'true')
-        {
-            translate.debugOutput(attrs,layerName,geometryType,'','Untangle attrs: ');
-            translate.debugOutput(tags,layerName,geometryType,'','Untangle tags: ');
-        }
+    // pre processing
+    tds40.applyToOsmPreProcessing(attrs, layerName, geometryType);
+
+    // Use the FCODE to add some tags
+    if (attrs.F_CODE)
+    {
+      var ftag = tds40.fcodeLookup['F_CODE'][attrs.F_CODE];
+      if (ftag)
+      {
+        tags[ftag[0]] = ftag[1];
+        // Debug: Dump out the tags from the FCODE
+        // print('FCODE: ' + attrs.F_CODE + ' tag=' + ftag[0] + '  value=' + ftag[1]);
+      }
+      else
+      {
+        hoot.logTrace('Translation for F_CODE ' + attrs.F_CODE + ' not found');
+      }
+    }
+
+    // Make a copy of the input attributes so we can remove them as they get translated. Looking at what
+    // isn't used in the translation - this should end up empty
+    // not in v8 yet: // var tTags = Object.assign({},tags);
+    var notUsedAttrs = (JSON.parse(JSON.stringify(attrs)));
+    delete notUsedAttrs.F_CODE;
+    delete notUsedAttrs.FCSUBTYPE;
+
+    // apply the simple number and text biased rules
+    // NOTE: We are not using the intList paramater for applySimpleNumBiased when going to OSM
+    translate.numToOSM(notUsedAttrs, tags, tds40.rules.numBiased);
+    translate.txtToOSM(notUsedAttrs, tags, tds40.rules.txtBiased);
+
+    // one 2 one
+    //translate.applyOne2One(notUsedAttrs, tags, tds40.lookup, {'k':'v'});
+    translate.applyOne2OneQuiet(notUsedAttrs, tags, tds40.lookup,{'k':'v'});
+
+    // Translate the XXX2, XXX3 etc attributes
+    translate.fix23Attr(notUsedAttrs, tags, tds40.lookup);
+
+    // Crack open the OTH field and populate the appropriate attributes
+    // The OTH format is _supposed_ to be (<attr>:<value>) but anything is possible
+    if (attrs.OTH) translate.processOTH(attrs, tags, tds40.lookup);
+
+    // post processing
+    tds40.applyToOsmPostProcessing(attrs, tags, layerName, geometryType);
+
+    // Debug: Add the FCODE to the tags
+    if (tds40.configIn.OgrDebugAddfcode == 'true') tags['raw:debugFcode'] = attrs.F_CODE;
+
+    // Override tag values if appropriate
+    translate.overrideValues(tags,tds40.toChange);
+
+    // Debug:
+    if (tds40.configIn.OgrDebugDumptags == 'true')
+    {
+      translate.debugOutput(notUsedAttrs,layerName,geometryType,'','Not used: ');
+      translate.debugOutput(tags,layerName,geometryType,'','Out tags: ');
+      print('');
+    }
+
+    return tags;
+  }, // End of toOsm
+
+
+  // This gets called by translateToOGR and is where the main work gets done
+  // We get Tags and return Attrs and a tableName
+  // This is the main routine to convert _TO_ TDS
+  toTds : function(tags, elementType, geometryType)
+  {
+    var tableName = ''; // The final table name
+    var returnData = []; // The array of features to return
+    var transMap = {}; // A map of translated attributes
+    attrs = {}; // The output attributes
+    attrs.F_CODE = ''; // Initial setup
+
+    // Setup config variables. We could do this in initialize() but some things don't call it :-(
+    // Doing this so we don't have to keep calling into Hoot core
+    if (tds40.configOut == undefined)
+    {
+      tds40.configOut = {};
+      tds40.configOut.OgrDebugDumptags = config.getOgrDebugDumptags();
+      tds40.configOut.OgrEsriFcsubtype = config.getOgrEsriFcsubtype();
+      tds40.configOut.OgrNoteExtra = config.getOgrNoteExtra();
+      tds40.configOut.OgrSplitO2s = config.getOgrSplitO2s();
+      tds40.configOut.OgrThematicStructure = config.getOgrThematicStructure();
+      tds40.configOut.OgrThrowError = config.getOgrThrowError();
+      tds40.configOut.OgrAddUuid = config.getOgrAddUuid();
+
+      // Get any changes to OSM tags
+      // NOTE: the rest of the config variables will change to this style of assignment soon
+      tds40.toChange = hoot.Settings.get('schema.translation.override');
+    }
+
+    // Check if we have a schema. This is a quick way to workout if various lookup tables have been built
+    if (tds40.rawSchema == undefined)
+    {
+      var tmp_schema = tds40.getDbSchema();
+    }
 
-        // pre processing
-        tds40.applyToOsmPreProcessing(attrs, layerName, geometryType);
+    // Start processing here
+    // Debug:
+    if (tds40.configOut.OgrDebugDumptags == 'true') translate.debugOutput(tags,'',geometryType,elementType,'In tags: ');
 
-        // Use the FCODE to add some tags
-        if (attrs.F_CODE)
+    // Use the FCODE to add some tags
+    if (attrs.F_CODE)
+    {
+        var ftag = tds40.fcodeLookup['F_CODE'][attrs.F_CODE];
+        if (ftag)
         {
-            var ftag = tds40.fcodeLookup['F_CODE'][attrs.F_CODE];
-            if (ftag)
+            if (!tags[ftag[0]])
             {
                 tags[ftag[0]] = ftag[1];
-                // Debug: Dump out the tags from the FCODE
-                // print('FCODE: ' + attrs.F_CODE + ' tag=' + ftag[0] + '  value=' + ftag[1]);
             }
             else
             {
-                hoot.logTrace('Translation for F_CODE ' + attrs.F_CODE + ' not found');
+                // Debug
+                hoot.logDebug('Tried to replace: ' + ftag[0] + '=' + tags[ftag[0]] + '  with ' + ftag[1]);
             }
+            // Debug: Dump out the tags from the FCODE
+            // print('FCODE: ' + attrs.F_CODE + ' tag=' + ftag[0] + '  value=' + ftag[1]);
         }
-
-        // Make a copy of the input attributes so we can remove them as they get translated. Looking at what
-        // isn't used in the translation - this should end up empty
-        // not in v8 yet: // var tTags = Object.assign({},tags);
-        var notUsedAttrs = (JSON.parse(JSON.stringify(attrs)));
-        delete notUsedAttrs.F_CODE;
-        delete notUsedAttrs.FCSUBTYPE;
-
-        // apply the simple number and text biased rules
-        // NOTE: We are not using the intList paramater for applySimpleNumBiased when going to OSM
-        translate.applySimpleNumBiased(notUsedAttrs, tags, tds40.rules.numBiased,'forward',[]);
-        translate.applySimpleTxtBiased(notUsedAttrs, tags, tds40.rules.txtBiased,'forward');
-
-        // one 2 one
-        //translate.applyOne2One(notUsedAttrs, tags, tds40.lookup, {'k':'v'});
-        translate.applyOne2OneQuiet(notUsedAttrs, tags, tds40.lookup);
-
-        // Translate the XXX2, XXX3 etc attributes
-        translate.fix23Attr(notUsedAttrs, tags, tds40.lookup);
-
-        // Crack open the OTH field and populate the appropriate attributes
-        // The OTH format is _supposed_ to be (<attr>:<value>) but anything is possible
-        if (attrs.OTH) translate.processOTH(attrs, tags, tds40.lookup);
-
-        // post processing
-        tds40.applyToOsmPostProcessing(attrs, tags, layerName, geometryType);
-
-        // Debug: Add the FCODE to the tags
-        if (tds40.configIn.OgrDebugAddfcode == 'true') tags['raw:debugFcode'] = attrs.F_CODE;
-
-        // Override tag values if appropriate
-        translate.overrideValues(tags,tds40.toChange);
-
-        // Debug:
-        if (tds40.configIn.OgrDebugDumptags == 'true')
+        else
         {
-            translate.debugOutput(notUsedAttrs,layerName,geometryType,'','Not used: ');
-            translate.debugOutput(tags,layerName,geometryType,'','Out tags: ');
-            print('');
+            hoot.logTrace('Translation for F_CODE ' + attrs.F_CODE + ' not found');
         }
+    }
 
-        return tags;
-    }, // End of toOsm
+    // The Nuke Option: If we have a relation, drop the feature and carry on
+    if (tags['building:part']) return null;
 
+    // The Nuke Option: "Collections" are groups of different feature types: Point, Area and Line
+    // There is no way we can translate these to a single TDS feature
+    if (geometryType == 'Collection') return null;
 
-    // This gets called by translateToOGR and is where the main work gets done
-    // We get Tags and return Attrs and a tableName
-    // This is the main routine to convert _TO_ TDS
-    toTds : function(tags, elementType, geometryType)
+    // Set up the fcode translation rules. We need this due to clashes between the one2one and
+    // the fcode one2one rules
+    if (tds40.fcodeLookup == undefined)
     {
-        var tableName = ''; // The final table name
-        var returnData = []; // The array of features to return
-        attrs = {}; // The output attributes
-        attrs.F_CODE = ''; // Initial setup
-
-        // Setup config variables. We could do this in initialize() but some things don't call it :-(
-        // Doing this so we don't have to keep calling into Hoot core
-        if (tds40.configOut == undefined)
-        {
-            tds40.configOut = {};
-            tds40.configOut.OgrDebugDumptags = config.getOgrDebugDumptags();
-            tds40.configOut.OgrEsriFcsubtype = config.getOgrEsriFcsubtype();
-            tds40.configOut.OgrNoteExtra = config.getOgrNoteExtra();
-            tds40.configOut.OgrSplitO2s = config.getOgrSplitO2s();
-            tds40.configOut.OgrThematicStructure = config.getOgrThematicStructure();
-            tds40.configOut.OgrThrowError = config.getOgrThrowError();
-            tds40.configOut.OgrAddUuid = config.getOgrAddUuid();
-
-            // Get any changes to OSM tags
-            // NOTE: the rest of the config variables will change to this style of assignment soon
-            tds40.toChange = hoot.Settings.get("schema.translation.override");
-        }
-
-        // Check if we have a schema. This is a quick way to workout if various lookup tables have been built
-        if (tds40.rawSchema == undefined)
-        {
-            var tmp_schema = tds40.getDbSchema();
-        }
-
-        // Start processing here
-        // Debug:
-        if (tds40.configOut.OgrDebugDumptags == 'true') translate.debugOutput(tags,'',geometryType,elementType,'In tags: ');
-
-        // The Nuke Option: If we have a relation, drop the feature and carry on
-        if (tags['building:part']) return null;
+      // Add the FCODE rules for Export
+      fcodeCommon.one2one.push.apply(fcodeCommon.one2one,tds40.rules.fcodeOne2oneOut);
 
-        // The Nuke Option: "Collections" are groups of different feature types: Point, Area and Line
-        // There is no way we can translate these to a single TDS feature
-        if (geometryType == 'Collection') return null;
+      tds40.fcodeLookup = translate.createBackwardsLookup(fcodeCommon.one2one);
+      // Debug
+      // translate.dumpOne2OneLookup(tds40.fcodeLookup);
+    }
 
-        // Set up the fcode translation rules. We need this due to clashes between the one2one and
-        // the fcode one2one rules
-        if (tds40.fcodeLookup == undefined)
-        {
-            // Add the FCODE rules for Export
-            fcodeCommon.one2one.push.apply(fcodeCommon.one2one,tds40.rules.fcodeOne2oneOut);
-
-            tds40.fcodeLookup = translate.createBackwardsLookup(fcodeCommon.one2one);
-            // Debug
-            // translate.dumpOne2OneLookup(tds40.fcodeLookup);
-        }
-
-        if (tds40.lookup == undefined)
-        {
-            // Add "other" rules to the one2one
-            tds40.rules.one2one.push.apply(tds40.rules.one2one,tds40.rules.one2oneOut);
+    if (tds40.lookup == undefined)
+    {
+      // Add "other" rules to the one2one
+      tds40.rules.one2one.push.apply(tds40.rules.one2one,tds40.rules.one2oneOut);
 
-            tds40.lookup = translate.createBackwardsLookup(tds40.rules.one2one);
-            // Debug
-            // translate.dumpOne2OneLookup(tds40.lookup);
-        }
+      tds40.lookup = translate.createBackwardsLookup(tds40.rules.one2one);
+      // Debug
+      // translate.dumpOne2OneLookup(tds40.lookup);
+    }
 
-        // Override values if appropriate
-        translate.overrideValues(tags,tds40.toChange);
+    // Override values if appropriate
+    translate.overrideValues(tags,tds40.toChange);
 
-        // Pre Processing
-        tds40.applyToTdsPreProcessing(tags, attrs, geometryType);
+    // Pre Processing
+    tds40.applyToTdsPreProcessing(tags, attrs, geometryType);
 
-        // Make a copy of the input tags so we can remove them as they get translated. What is left is
-        // the not used tags
-        // not in v8 yet: // var tTags = Object.assign({},tags);
-        var notUsedTags = (JSON.parse(JSON.stringify(tags)));
+    // Make a copy of the input tags so we can remove them as they get translated. What is left is
+    // the not used tags
+    // not in v8 yet: // var tTags = Object.assign({},tags);
+    var notUsedTags = (JSON.parse(JSON.stringify(tags)));
 
-        if (notUsedTags.hoot) delete notUsedTags.hoot; // Added by the UI
-        // Debug info. We use this in postprocessing via "tags"
-        if (notUsedTags['hoot:id']) delete notUsedTags['hoot:id'];
+    if (notUsedTags.hoot) delete notUsedTags.hoot; // Added by the UI
+    // Debug info. We use this in postprocessing via "tags"
+    if (notUsedTags['hoot:id']) delete notUsedTags['hoot:id'];
 
-        // Apply the simple number and text biased rules
-        // NOTE: These are BACKWARD, not forward!
-        // NOTE: These delete tags as they are used
-        translate.applySimpleNumBiased(attrs, notUsedTags, tds40.rules.numBiased,'backward',tds40.rules.intList);
-        translate.applySimpleTxtBiased(attrs, notUsedTags, tds40.rules.txtBiased,'backward');
+    // Apply the simple number and text biased rules
+    // NOTE: These are BACKWARD, not forward!
+    // NOTE: These delete tags as they are used
+    translate.numToOgr(attrs, notUsedTags, tds40.rules.numBiased,tds40.rules.intList,transMap);
+    translate.txtToOgr(attrs, notUsedTags, tds40.rules.txtBiased,transMap);
 
-        // Translate the XXX:2, XXX2, XXX:3 etc attributes
-        // Note: This deletes tags as they are used
-        translate.fix23Tags(notUsedTags, attrs, tds40.lookup);
+    // Translate the XXX:2, XXX2, XXX:3 etc attributes
+    // Note: This deletes tags as they are used
+    translate.fix23Tags(notUsedTags, attrs, tds40.lookup);
 
-        // one 2 one - we call the version that knows about the OTH field
-        // NOTE: This deletes tags as they are used
-        translate.applyTdsOne2One(notUsedTags, attrs, tds40.lookup, tds40.fcodeLookup);
+    // one 2 one - we call the version that knows about the OTH field
+    // NOTE: This deletes tags as they are used
+    translate.applyTdsOne2One(notUsedTags, attrs, tds40.lookup, tds40.fcodeLookup,transMap);
 
-        // Post Processing
-        // We send the original list of tags and the list of tags we haven't used yet
-        tds40.applyToTdsPostProcessing(tags, attrs, geometryType, notUsedTags);
+    // Post Processing
+    // We send the original list of tags and the list of tags we haven't used yet
+    tds40.applyToTdsPostProcessing(tags, attrs, geometryType, notUsedTags);
 
-        // Debug
-        if (tds40.configOut.OgrDebugDumptags == 'true') translate.debugOutput(notUsedTags,'',geometryType,elementType,'Not used: ');
+    // Debug
+    if (tds40.configOut.OgrDebugDumptags == 'true') translate.debugOutput(notUsedTags,'',geometryType,elementType,'Not used: ');
 
-        // If we have unused tags, add them to the memo field
-        if (Object.keys(notUsedTags).length > 0 && tds40.configOut.OgrNoteExtra == 'attribute')
-        {
-            var tStr = '<OSM>' + JSON.stringify(notUsedTags) + '</OSM>';
-            attrs.ZI006_MEM = translate.appendValue(attrs.ZI006_MEM,tStr,';');
-        }
+    // If we have unused tags, add them to the memo field
+    if (Object.keys(notUsedTags).length > 0 && tds40.configOut.OgrNoteExtra == 'attribute')
+    {
+      var tStr = '<OSM>' + JSON.stringify(notUsedTags) + '</OSM>';
+      attrs.ZI006_MEM = translate.appendValue(attrs.ZI006_MEM,tStr,';');
+    }
 
-        // Now check for invalid feature geometry
-        // E.g. If the spec says a runway is a polygon and we have a line, throw error and
-        // push the feature to o2s layer
-        var gFcode = geometryType.toString().charAt(0) + attrs.F_CODE;
+    // Now check for invalid feature geometry
+    // E.g. If the spec says a runway is a polygon and we have a line, throw error and
+    // push the feature to o2s layer
+    var gFcode = geometryType.toString().charAt(0) + attrs.F_CODE;
 
+    if (tds40.AttrLookup[gFcode.toUpperCase()])
+    {
+      // Check if we need to make more features
+      // NOTE: This returns the structure we are going to send back to Hoot:  {attrs: attrs, tableName: 'Name'}
+      returnData = tds40.manyFeatures(geometryType,tags,attrs,transMap);
+
+      // Debug: Add the first feature
+      //returnData.push({attrs: attrs, tableName: ''});
+
+      // Now go through the features and clean them up
+      var gType = geometryType.toString().charAt(0);
+      for (var i = 0, fLen = returnData.length; i < fLen; i++)
+      {
+        // Make sure that we have a valid FCODE
+        var gFcode = gType + returnData[i]['attrs']['F_CODE'];
         if (tds40.AttrLookup[gFcode.toUpperCase()])
         {
-            // Check if we need to make more features
-            // NOTE: This returns the structure we are going to send back to Hoot:  {attrs: attrs, tableName: 'Name'}
-            returnData = tds40.manyFeatures(geometryType,tags,attrs);
-
-            // Debug: Add the first feature
-            //returnData.push({attrs: attrs, tableName: ''});
-
-            // Now go through the features and clean them up
-            var gType = geometryType.toString().charAt(0);
-            for (var i = 0, fLen = returnData.length; i < fLen; i++)
-            {
-                // Make sure that we have a valid FCODE
-                var gFcode = gType + returnData[i]['attrs']['F_CODE'];
-                if (tds40.AttrLookup[gFcode.toUpperCase()])
-                {
-                    // Validate attrs: remove all that are not supposed to be part of a feature
-                    tds40.validateAttrs(geometryType,returnData[i]['attrs']);
-
-                    // Now set the FCSubtype
-                    // NOTE: If we export to shapefile, GAIT _will_ complain about this
-                    if (tds40.configOut.OgrEsriFcsubtype == 'true')
-                    {
-                        returnData[i]['attrs']['FCSUBTYPE'] = tds40.rules.subtypeList[returnData[i]['attrs']['F_CODE']];
-                    }
-
-                    // If we are using the TDS structre, fill the rest of the unused attrs in the schema
-                    if (tds40.configOut.OgrThematicStructure == 'true')
-                    {
-                        returnData[i]['tableName'] = tds40.rules.thematicGroupList[gFcode];
-                        tds40.validateTDSAttrs(gFcode, returnData[i]['attrs']);
-                    }
-                    else
-                    {
-                        returnData[i]['tableName'] = tds40.layerNameLookup[gFcode.toUpperCase()];
-                    }
-                }
-                else
-                {
-                    // If the feature is not valid, just drop it
-                    // Debug
-                    // print('## Skipping: ' + gFcode);
-                    returnData.splice(i,1);
-                    fLen = returnData.length;
-                }
-            } // End returnData loop
-
-            // If we have unused tags, throw them into the "extra" layer
-            if (Object.keys(notUsedTags).length > 0 && tds40.configOut.OgrNoteExtra == 'file')
-            {
-                var extraFeature = {};
-                extraFeature.tags = JSON.stringify(notUsedTags);
-                extraFeature.uuid = attrs.UFI;
-
-                var extraName = 'extra_' + geometryType.toString().charAt(0);
+          // Validate attrs: remove all that are not supposed to be part of a feature
+          tds40.validateAttrs(geometryType,returnData[i]['attrs'],notUsedTags,transMap);
 
-                returnData.push({attrs: extraFeature, tableName: extraName});
-            } // End notUsedTags
-
-            // Look for Review tags and push them to a review layer if found
-            if (tags['hoot:review:needs'] == 'yes')
-            {
-                var reviewAttrs = {};
-
-                // Note: Some of these may be "undefined"
-                reviewAttrs.note = tags['hoot:review:note'];
-                reviewAttrs.score = tags['hoot:review:score'];
-                reviewAttrs.uuid = tags.uuid;
-                reviewAttrs.source = tags['hoot:review:source'];
+          // Now set the FCSubtype
+          // NOTE: If we export to shapefile, GAIT _will_ complain about this
+          if (tds40.configOut.OgrEsriFcsubtype == 'true')
+          {
+            returnData[i]['attrs']['FCSUBTYPE'] = tds40.rules.subtypeList[returnData[i]['attrs']['F_CODE']];
+          }
 
-                var reviewTable = 'review_' + geometryType.toString().charAt(0);
-                returnData.push({attrs: reviewAttrs, tableName: reviewTable});
-            } // End ReviewTags
+          // If we are using the TDS structre, fill the rest of the unused attrs in the schema
+          if (tds40.configOut.OgrThematicStructure == 'true')
+          {
+            returnData[i]['tableName'] = tds40.rules.thematicGroupList[gFcode];
+            tds40.validateTDSAttrs(gFcode, returnData[i]['attrs']);
+          }
+          else
+          {
+            returnData[i]['tableName'] = tds40.layerNameLookup[gFcode.toUpperCase()];
+          }
         }
-        else // We DON'T have a feature
+        else
         {
-            // For the UI: Throw an error and die if we don't have a valid feature
-            if (tds40.configOut.OgrThrowError == 'true')
-            {
-                if (! attrs.F_CODE)
-                {
-                    returnData.push({attrs:{'error':'No Valid Feature Code'}, tableName: ''});
-                    return returnData;
-                }
-                else
-                {
-                    //throw new Error(geometryType.toString() + ' geometry is not valid for F_CODE ' + attrs.F_CODE);
-                    returnData.push({attrs:{'error':geometryType + ' geometry is not valid for ' + attrs.F_CODE + ' in TDSv40'}, tableName: ''});
-                    return returnData;
-                }
-            }
+          // If the feature is not valid, just drop it
+          // Debug
+          // print('## Skipping: ' + gFcode);
+          returnData.splice(i,1);
+          fLen = returnData.length;
+        }
+      } // End returnData loop
+
+      // If we have unused tags, throw them into the "extra" layer
+      if (Object.keys(notUsedTags).length > 0 && tds40.configOut.OgrNoteExtra == 'file')
+      {
+        var extraFeature = {};
+        extraFeature.tags = JSON.stringify(notUsedTags);
+        extraFeature.uuid = attrs.UFI;
+
+        var extraName = 'extra_' + geometryType.toString().charAt(0);
+
+        returnData.push({attrs: extraFeature, tableName: extraName});
+      } // End notUsedTags
+
+      // Look for Review tags and push them to a review layer if found
+      if (tags['hoot:review:needs'] == 'yes')
+      {
+        var reviewAttrs = {};
+
+        // Note: Some of these may be "undefined"
+        reviewAttrs.note = tags['hoot:review:note'];
+        reviewAttrs.score = tags['hoot:review:score'];
+        reviewAttrs.uuid = tags.uuid;
+        reviewAttrs.source = tags['hoot:review:source'];
+
+        var reviewTable = 'review_' + geometryType.toString().charAt(0);
+        returnData.push({attrs: reviewAttrs, tableName: reviewTable});
+      } // End ReviewTags
+    }
+    else // We DON'T have a feature
+    {
+      // For the UI: Throw an error and die if we don't have a valid feature
+      if (tds40.configOut.OgrThrowError == 'true')
+      {
+        if (! attrs.F_CODE)
+        {
+          returnData.push({attrs:{'error':'No Valid Feature Code'}, tableName: ''});
+          return returnData;
+        }
+        else
+        {
+          //throw new Error(geometryType.toString() + ' geometry is not valid for F_CODE ' + attrs.F_CODE);
+          returnData.push({attrs:{'error':geometryType + ' geometry is not valid for ' + attrs.F_CODE + ' in TDSv40'}, tableName: ''});
+          return returnData;
+        }
+      }
 
-            hoot.logTrace('FCODE and Geometry: ' + gFcode + ' is not in the schema');
+      hoot.logTrace('FCODE and Geometry: ' + gFcode + ' is not in the schema');
 
-            tableName = 'o2s_' + geometryType.toString().charAt(0);
+      tableName = 'o2s_' + geometryType.toString().charAt(0);
 
-            // Debug:
-            // Dump out what attributes we have converted before they get wiped out
-            if (tds40.configOut.OgrDebugDumptags == 'true') translate.debugOutput(attrs,'',geometryType,elementType,'Converted attrs: ');
+      // Debug:
+      // Dump out what attributes we have converted before they get wiped out
+      if (tds40.configOut.OgrDebugDumptags == 'true') translate.debugOutput(attrs,'',geometryType,elementType,'Converted attrs: ');
 
-            // We want to keep the hoot:id if present
-            if (tags['hoot:id'])
-            {
-                tags.raw_id = tags['hoot:id'];
-                delete tags['hoot:id'];
-            }
+      // We want to keep the hoot:id if present
+      if (tags['hoot:id'])
+      {
+        tags.raw_id = tags['hoot:id'];
+        delete tags['hoot:id'];
+      }
 
-            // Convert all of the Tags to a string so we can jam it into an attribute
-            // var str = JSON.stringify(tags);
-            var str = JSON.stringify(tags,Object.keys(tags).sort());
+      // Convert all of the Tags to a string so we can jam it into an attribute
+      // var str = JSON.stringify(tags);
+      var str = JSON.stringify(tags,Object.keys(tags).sort());
 
-            // Shapefiles can't handle fields > 254 chars
-            // If the tags are > 254 char, split into pieces. Not pretty but stops errors
-            // A nicer thing would be to arrange the tags until they fit neatly
-            if (str.length < 255 || tds40.configOut.OgrSplitO2s == 'false')
-            {
-                // return {attrs:{tag1:str}, tableName: tableName};
-                attrs = {tag1:str};
-            }
-            else
-            {
-                // Not good. Will fix with the rewrite of the tag splitting code
-                if (str.length > 1012)
-                {
-                    hoot.logTrace('o2s tags truncated to fit in available space.');
-                    str = str.substring(0,1012);
-                }
+      // Shapefiles can't handle fields > 254 chars
+      // If the tags are > 254 char, split into pieces. Not pretty but stops errors
+      // A nicer thing would be to arrange the tags until they fit neatly
+      if (str.length < 255 || tds40.configOut.OgrSplitO2s == 'false')
+      {
+        // return {attrs:{tag1:str}, tableName: tableName};
+        attrs = {tag1:str};
+      }
+      else
+      {
+        // Not good. Will fix with the rewrite of the tag splitting code
+        if (str.length > 1012)
+        {
+          hoot.logTrace('o2s tags truncated to fit in available space.');
+          str = str.substring(0,1012);
+        }
 
-                // return {attrs:{tag1:str.substring(0,253), tag2:str.substring(253)}, tableName: tableName};
-                attrs = {tag1:str.substring(0,253),
-                        tag2:str.substring(253,506),
-                        tag3:str.substring(506,759),
-                        tag4:str.substring(759,1012)};
-            }
+        // return {attrs:{tag1:str.substring(0,253), tag2:str.substring(253)}, tableName: tableName};
+        attrs = {tag1:str.substring(0,253),
+          tag2:str.substring(253,506),
+          tag3:str.substring(506,759),
+          tag4:str.substring(759,1012)};
+      }
 
-            returnData.push({attrs: attrs, tableName: tableName});
-        } // End else We DON'T have a feature
+      returnData.push({attrs: attrs, tableName: tableName});
+    } // End else We DON'T have a feature
 
-        // Debug:
-        if (tds40.configOut.OgrDebugDumptags == 'true')
-        {
-            for (var i = 0, fLen = returnData.length; i < fLen; i++)
-            {
-                print('TableName ' + i + ': ' + returnData[i]['tableName'] + '  FCode: ' + returnData[i]['attrs']['F_CODE'] + '  Geom: ' + geometryType);
-                translate.debugOutput(returnData[i]['attrs'],'',geometryType,elementType,'Out attrs: ');
-            }
-            print('');
-        }
+    // Debug:
+    if (tds40.configOut.OgrDebugDumptags == 'true')
+    {
+      for (var i = 0, fLen = returnData.length; i < fLen; i++)
+      {
+        print('TableName ' + i + ': ' + returnData[i]['tableName'] + '  FCode: ' + returnData[i]['attrs']['F_CODE'] + '  Geom: ' + geometryType);
+        translate.debugOutput(returnData[i]['attrs'],'',geometryType,elementType,'Out attrs: ');
+      }
+      print('');
+    }
 
-        return returnData;
+    return returnData;
 
-    } // End of toTds
+  } // End of toTds
 
-} // End of tds40
+}; // End of tds40
Clone this wiki locally