{
  "id": "OcAQrF91wzWH3rOw",
  "name": "Generate and Decode DIGIPIN",
  "tags": [],
  "nodes": [
    {
      "id": "882aa556-8ea2-4582-8230-7b0e13ad2fd1",
      "name": "Respond to Webhook - Success",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        340,
        -140
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "json",
        "responseBody": "={\n  \"status\": \"Success\",\n  \"lat\": \"{{ $json.query.lat }}\",\n  \"lon\": \"{{ $json.query.lon }}\",\n  \"digipin\": \"{{ $json.digipin }}\"\n} "
      },
      "typeVersion": 1.4
    },
    {
      "id": "368240b0-cecf-46e7-9509-bfcb1afe7610",
      "name": "Respond to Webhook - Error",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        340,
        60
      ],
      "parameters": {
        "options": {
          "responseCode": 422
        },
        "respondWith": "json",
        "responseBody": "={\n  \"status\": \"Failed\",\n  \"lat\": \"{{ $json.query.lat }}\",\n  \"lon\": \"{{ $json.query.lon }}\",\n  \"error\": \"{{ $json.error }}\"\n} "
      },
      "typeVersion": 1.4
    },
    {
      "id": "b6c70cd6-3d5f-4215-989c-8a5e6a896564",
      "name": "Switch - Check for Success",
      "type": "n8n-nodes-base.switch",
      "position": [
        120,
        -40
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "digipin",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "3656e246-523a-4611-b563-8e66ae078d67",
                    "operator": {
                      "type": "string",
                      "operation": "exists",
                      "singleValue": true
                    },
                    "leftValue": "={{ $json.digipin }}",
                    "rightValue": ""
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "9b5ef11d-7833-41c3-8611-56cf11df2dcc",
                    "operator": {
                      "type": "string",
                      "operation": "exists",
                      "singleValue": true
                    },
                    "leftValue": "={{ $json.error }}",
                    "rightValue": ""
                  }
                ]
              }
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "3a7dc7a2-9d19-467c-b651-143187d852d9",
      "name": "DIGIPIN_Generation_Code",
      "type": "n8n-nodes-base.code",
      "position": [
        -100,
        -40
      ],
      "parameters": {
        "jsCode": "// --- DIGIPIN Generation Logic ---\n\n// Constants for DIGIPIN generation\nconst DIGIPIN_GRID = [\n    ['F', 'C', '9', '8'],\n    ['J', '3', '2', '7'],\n    ['K', '4', '5', '6'],\n    ['L', 'M', 'P', 'T']\n];\n\nconst BOUNDS = {\n    minLat: 2.5,\n    maxLat: 38.5,\n    minLon: 63.5,\n    maxLon: 99.5\n};\n\n/**\n * Encodes latitude and longitude into a DIGIPIN.\n * @param {number} lat The latitude.\n * @param {number} lon The longitude.\n * @returns {string} The generated DIGIPIN.\n * @throws {Error} If coordinates are out of bounds.\n */\nfunction generateDigiPin(lat, lon) {\n    if (lat < BOUNDS.minLat || lat > BOUNDS.maxLat || lon < BOUNDS.minLon || lon > BOUNDS.maxLon) {\n        throw new Error(`Coordinates (Lat: ${lat}, Lon: ${lon}) are out of the valid range.`);\n    }\n\n    let minLat = BOUNDS.minLat, maxLat = BOUNDS.maxLat;\n    let minLon = BOUNDS.minLon, maxLon = BOUNDS.maxLon;\n    let digiPin = '';\n\n    for (let level = 1; level <= 10; level++) {\n        const latDiv = (maxLat - minLat) / 4;\n        const lonDiv = (maxLon - minLon) / 4;\n        let row = 3 - Math.floor((lat - minLat) / latDiv);\n        let col = Math.floor((lon - minLon) / lonDiv);\n        row = Math.max(0, Math.min(row, 3));\n        col = Math.max(0, Math.min(col, 3));\n        digiPin += DIGIPIN_GRID[row][col];\n        if (level === 3 || level === 6) {\n            digiPin += '-';\n        }\n        maxLat = minLat + latDiv * (4 - row);\n        minLat = minLat + latDiv * (3 - row);\n        minLon = minLon + lonDiv * col;\n        maxLon = minLon + lonDiv;\n    }\n    return digiPin;\n}\n\n\n// --- n8n Item Processing ---\n// Loop through each input item provided to the node.\nfor (const item of items) {\n    try {\n        // Get latitude and longitude from the input item's JSON data.\n        // Assumes the previous node provides these fields.\n        const lat = $input.first().json.query.lat;\n        const lon = $input.first().json.query.lon;\n\n        if (lat === undefined || lon === undefined) {\n            throw new Error(\"Input item is missing 'latitude' or 'longitude' field.\");\n        }\n\n        // Generate the DIGIPIN.\n        const digipinResult = generateDigiPin(lat, lon);\n\n        // Add the result to a new field called 'digipin' in the item's JSON.\n        item.json.digipin = digipinResult;\n\n    } catch (error) {\n        // If an error occurs, add an 'error' field to the item.\n        // This helps with debugging in the n8n UI.\n        item.json.error = error.message;\n    }\n}\n\n// Return all the modified items to the next node in the workflow.\nreturn items;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "33aab4da-4b77-44fa-85c4-0ed165b416d3",
      "name": "DIGIPIN_Decode_Code",
      "type": "n8n-nodes-base.code",
      "position": [
        -100,
        420
      ],
      "parameters": {
        "jsCode": "/**\n * Decodes a DIGIPIN into latitude and longitude coordinates.\n * @param {string} digiPin The DIGIPIN to decode.\n * @returns {{latitude: string, longitude: string}} An object containing the latitude and longitude,\n * each formatted to 6 decimal places.\n * @throws {Error} If the DIGIPIN is invalid or contains invalid characters.\n */\nconst DIGIPIN_GRID = [\n    ['F', 'C', '9', '8'],\n    ['J', '3', '2', '7'],\n    ['K', '4', '5', '6'],\n    ['L', 'M', 'P', 'T']\n];\n\n\nconst BOUNDS = {\n    minLat: 2.5,\n    maxLat: 38.5,\n    minLon: 63.5,\n    maxLon: 99.5\n};\nfunction getLatLngFromDigiPin(digiPin) {\n    const pin = digiPin.replace(/-/g, ''); // Remove hyphens for processing\n    if (pin.length !== 10) throw new Error('Invalid DIGIPIN: Must be 10 characters long after removing hyphens.');\n\n    let minLat = BOUNDS.minLat;\n    let maxLat = BOUNDS.maxLat;\n    let minLon = BOUNDS.minLon;\n    let maxLon = BOUNDS.minLon; // Corrected initial value here, should be BOUNDS.minLon\n\n    // Recalculate maxLon for accurate initial bounds\n    // Based on the original DIGIPIN generation, it should cover the whole range initially.\n    maxLon = BOUNDS.maxLon;\n\n\n    for (let i = 0; i < 10; i++) {\n        const char = pin[i];\n        let found = false;\n        let ri = -1, ci = -1;\n\n        for (let r = 0; r < 4; r++) {\n            for (let c = 0; c < 4; c++) {\n                if (DIGIPIN_GRID[r][c] === char) {\n                    ri = r;\n                    ci = c;\n                    found = true;\n                    break;\n                }\n            }\n            if (found) break;\n        }\n\n        if (!found) throw new Error(`Invalid character '${char}' in DIGIPIN.`);\n\n        const latDiv = (maxLat - minLat) / 4;\n        const lonDiv = (maxLon - minLon) / 4;\n\n        const lat1 = maxLat - latDiv * (ri + 1);\n        const lat2 = maxLat - latDiv * ri;\n        const lon1 = minLon + lonDiv * ci;\n        const lon2 = minLon + lonDiv * (ci + 1);\n\n        minLat = lat1;\n        maxLat = lat2;\n        minLon = lon1;\n        maxLon = lon2;\n    }\n\n    const centerLat = (minLat + maxLat) / 2;\n    const centerLon = (minLon + maxLon) / 2;\n\n    return {\n        latitude: centerLat.toFixed(6),\n        longitude: centerLon.toFixed(6)\n    };\n}\n\n\nconst MY_DIGIPIN = $input.first().json.query.digipin; // <--- REPLACE THIS with your desired DIGIPIN\n\n// Ensure there's at least one item to process, or create a dummy one\nlet outputItems = [];\nif (items.length === 0) {\n    // If no items come into the node, create a blank one to attach results to\n    outputItems.push({ json: {} });\n} else {\n    // If items exist, clone them to add results\n    outputItems = JSON.parse(JSON.stringify(items));\n}\n\nfor (const item of outputItems) {\n    try {\n        const digipinToProcess = MY_DIGIPIN;\n\n        if (!digipinToProcess) {\n            throw new Error(\"No DIGIPIN specified for processing.\");\n        }\n\n        // Decode the DIGIPIN\n        const decodedCoords = getLatLngFromDigiPin(digipinToProcess);\n\n        // Add the result to new fields in the item's JSON\n        item.json.processedDigipin = digipinToProcess; // Optional: To see which digipin was processed\n        item.json.decodedLatitude = decodedCoords.latitude;\n        item.json.decodedLongitude = decodedCoords.longitude;\n\n    } catch (error) {\n        // If an error occurs, add an 'error' field to the item.\n        item.json.error = error.message;\n    }\n}\n\n// Return all the modified items to the next node in the workflow.\nreturn outputItems;"
      },
      "typeVersion": 2
    },
    {
      "id": "6da5b090-caf7-4a8b-a254-a90ac9b97610",
      "name": "Encode_Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -320,
        -40
      ],
      "parameters": {
        "path": "generate-digipin",
        "options": {},
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "99a30d16-696a-4c2c-941d-7b66a6591d54",
      "name": "Decode_Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -320,
        420
      ],
      "parameters": {
        "path": "decode-digipin",
        "options": {},
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "ab871baf-aae7-4060-83af-7794974dce18",
      "name": "Switch 2 - Check for Success1",
      "type": "n8n-nodes-base.switch",
      "position": [
        120,
        420
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "coordinates",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "3656e246-523a-4611-b563-8e66ae078d67",
                    "operator": {
                      "type": "string",
                      "operation": "exists",
                      "singleValue": true
                    },
                    "leftValue": "={{ $json.decodedLatitude }}",
                    "rightValue": ""
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "9b5ef11d-7833-41c3-8611-56cf11df2dcc",
                    "operator": {
                      "type": "string",
                      "operation": "exists",
                      "singleValue": true
                    },
                    "leftValue": "={{ $json.error }}",
                    "rightValue": ""
                  }
                ]
              }
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "64409e6c-e4aa-41b2-abcf-a016e729ea37",
      "name": "Respond to Webhook - Success 2",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        340,
        320
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "json",
        "responseBody": "={\n  \"status\": \"Success\",\n  \"digipin\": \"{{ $json.query.digipin }}\",\n  \"lat\": \"{{ $json.decodedLatitude }}\",\n  \"lon\": \"{{ $json.decodedLongitude }}\"\n} "
      },
      "typeVersion": 1.4
    },
    {
      "id": "d37184ce-7060-4d06-b039-dd8572e7d3e8",
      "name": "Respond to Webhook - Error 2",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        340,
        520
      ],
      "parameters": {
        "options": {
          "responseCode": 422
        },
        "respondWith": "json",
        "responseBody": "={\n  \"status\": \"Failed\",\n  \"digipin\": \"{{ $json.query.digipin }}\",\n  \"error\": \"{{ $json.error }}\"\n} "
      },
      "typeVersion": 1.4
    },
    {
      "id": "8d745b4f-f993-4865-a96f-e3f1e28a0cc2",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        660,
        360
      ],
      "parameters": {
        "color": 4,
        "width": 920,
        "height": 320,
        "content": "## Example usage \n\nGenerate a DIGIPIN:\n```\ncurl --request GET \\\n  --url 'https://n8n.example.in/webhook/generate-digipin?lat=27.175063&lon=78.042169'\n```\n\nDecode a DIGIPIN:\n```\ncurl --request GET \\\n  --url 'https://n8n.example.in/webhook/decode-digipin?digipin=32C-849-5CJ6'\n```"
      },
      "typeVersion": 1
    },
    {
      "id": "3cb47a09-ce41-4d2d-a413-885510ebda96",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        660,
        -160
      ],
      "parameters": {
        "width": 920,
        "height": 440,
        "content": "## DIGIPIN Generator and Decoder\n\nThis n8n workflow lets you **generate and decode DIGIPINs** using only JavaScript - no external APIs involved.\n\nDIGIPIN is India Post\u2019s geolocation code system that encodes latitude and longitude into a 10-character alphanumeric string, like `32C-849-5CJ6`. It helps simplify precise location sharing across India.\n\n### \ud83d\udd27 What it does\n- `/generate-digipin?lat=...&lon=...` \u2192 returns the DIGIPIN for given coordinates\n- `/decode-digipin?digipin=...` \u2192 returns the coordinates for a given DIGIPIN\n\nUseful for: check-in systems, last-mile delivery, digital address verification, or simple location sharing.\n\n### \u2699\ufe0f How to use\n- Add your own webhook URLs or run locally\n- Pass query parameters to trigger either route\n- Both encode/decode logic is handled inside Function nodes\n\nNo credentials, APIs, or setup required.\n"
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "9226c009-d942-4f3c-b982-9f7dbb73d710",
  "connections": {
    "Decode_Webhook": {
      "main": [
        [
          {
            "node": "DIGIPIN_Decode_Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Encode_Webhook": {
      "main": [
        [
          {
            "node": "DIGIPIN_Generation_Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "DIGIPIN_Decode_Code": {
      "main": [
        [
          {
            "node": "Switch 2 - Check for Success1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "DIGIPIN_Generation_Code": {
      "main": [
        [
          {
            "node": "Switch - Check for Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch - Check for Success": {
      "main": [
        [
          {
            "node": "Respond to Webhook - Success",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond to Webhook - Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch 2 - Check for Success1": {
      "main": [
        [
          {
            "node": "Respond to Webhook - Success 2",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond to Webhook - Error 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}