{
  "name": "Find nearby businesses by proximity using OpenStreetMap and save to Google Sheets",
  "nodes": [
    {
      "parameters": {
        "formTitle": "Local business discovery by proximity",
        "formDescription": "Find businesses near a location and record which ones list a website.",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Location",
              "placeholder": "City or address, e.g. Kingston, Ontario",
              "requiredField": true
            },
            {
              "fieldLabel": "Business category",
              "fieldType": "dropdown",
              "fieldOptions": {
                "values": [
                  {
                    "option": "Restaurants"
                  },
                  {
                    "option": "Cafes & coffee shops"
                  },
                  {
                    "option": "Bars & pubs"
                  },
                  {
                    "option": "Hair & beauty salons"
                  },
                  {
                    "option": "Gyms & fitness"
                  },
                  {
                    "option": "Auto repair shops"
                  },
                  {
                    "option": "Dentists"
                  },
                  {
                    "option": "Florists"
                  },
                  {
                    "option": "Real estate agents"
                  },
                  {
                    "option": "Retail shops (any)"
                  }
                ]
              },
              "requiredField": true
            },
            {
              "fieldLabel": "Search radius",
              "fieldType": "dropdown",
              "defaultValue": "2000",
              "fieldOptions": {
                "values": [
                  {
                    "option": "500"
                  },
                  {
                    "option": "1000"
                  },
                  {
                    "option": "2000"
                  },
                  {
                    "option": "5000"
                  }
                ]
              },
              "requiredField": true
            },
            {
              "fieldLabel": "Max results",
              "fieldType": "number",
              "defaultValue": "50",
              "requiredField": true
            }
          ]
        },
        "options": {
          "appendAttribution": false
        }
      },
      "id": "92684d46-54bb-4c5d-825a-36ca433269f7",
      "name": "Search request",
      "type": "n8n-nodes-base.formTrigger",
      "typeVersion": 2.6,
      "position": [
        128,
        368
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a1",
              "name": "nominatimUrl",
              "value": "https://nominatim.openstreetmap.org",
              "type": "string"
            },
            {
              "id": "a2",
              "name": "overpassUrl",
              "value": "https://overpass-api.de/api/interpreter",
              "type": "string"
            },
            {
              "id": "a3",
              "name": "userAgent",
              "value": "n8n local business discovery (contact: your-email@example.com)",
              "type": "string"
            },
            {
              "id": "a4",
              "name": "searchLocation",
              "value": "={{ $json.Location }}",
              "type": "string"
            },
            {
              "id": "a5",
              "name": "category",
              "value": "={{ $json['Business category'] }}",
              "type": "string"
            },
            {
              "id": "a6",
              "name": "radiusMeters",
              "value": "={{ $json['Search radius'] }}",
              "type": "string"
            },
            {
              "id": "a7",
              "name": "maxResults",
              "value": "={{ $json['Max results'] }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "e0691771-8dcf-4c58-b417-48fdce42605b",
      "name": "Settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        304,
        368
      ]
    },
    {
      "parameters": {
        "url": "={{ $json.nominatimUrl }}/search",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "q",
              "value": "={{ $json.searchLocation }}"
            },
            {
              "name": "format",
              "value": "json"
            },
            {
              "name": "limit",
              "value": "1"
            }
          ]
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "={{ $json.userAgent }}"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "5ea35547-f898-4909-9373-b0a87da52ce7",
      "name": "Geocode location (Nominatim)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        496,
        368
      ],
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 1
          },
          "conditions": [
            {
              "leftValue": "={{ $json.lat }}",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "id": "16718154-fe0f-4738-9137-a3b1fda36c4a",
      "name": "Location found?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        656,
        368
      ]
    },
    {
      "parameters": {
        "jsCode": "const settings = $('Settings').first().json;\nconst lat = $json.lat;\nconst lon = $json.lon;\nconst radius = Number(settings.radiusMeters) || 2000;\nconst category = settings.category;\n\nconst tagMap = {\n  'Restaurants': '[\"amenity\"=\"restaurant\"]',\n  'Cafes & coffee shops': '[\"amenity\"=\"cafe\"]',\n  'Bars & pubs': '[\"amenity\"~\"bar|pub\"]',\n  'Hair & beauty salons': '[\"shop\"~\"hairdresser|beauty\"]',\n  'Gyms & fitness': '[\"leisure\"=\"fitness_centre\"]',\n  'Auto repair shops': '[\"shop\"=\"car_repair\"]',\n  'Dentists': '[\"amenity\"=\"dentist\"]',\n  'Florists': '[\"shop\"=\"florist\"]',\n  'Real estate agents': '[\"office\"=\"estate_agent\"]',\n  'Retail shops (any)': '[\"shop\"]'\n};\nconst tag = tagMap[category] || '[\"shop\"]';\n\nconst query = '[out:json][timeout:25];\\n(\\n  node' + tag + '(around:' + radius + ',' + lat + ',' + lon + ');\\n  way' + tag + '(around:' + radius + ',' + lat + ',' + lon + ');\\n);\\nout center tags;';\n\nreturn [{ json: { overpassQuery: query, overpassUrl: settings.overpassUrl, userAgent: settings.userAgent } }];"
      },
      "id": "dcc593c0-4a7a-465a-8ddd-500d433c3bb9",
      "name": "Build Overpass query",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        848,
        352
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $json.overpassUrl }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "={{ $json.userAgent }}"
            }
          ]
        },
        "sendBody": true,
        "contentType": "form-urlencoded",
        "bodyParameters": {
          "parameters": [
            {
              "name": "data",
              "value": "={{ $json.overpassQuery }}"
            }
          ]
        },
        "options": {}
      },
      "id": "372cc414-99d5-4578-a12d-31c129824914",
      "name": "Find businesses (Overpass)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        1008,
        352
      ]
    },
    {
      "parameters": {
        "jsCode": "const settings = $('Settings').first().json;\nconst elements = $json.elements || [];\nconst max = Number(settings.maxResults) || 50;\n\nconst results = [];\nfor (const el of elements) {\n  const t = el.tags || {};\n  const website = t.website || t['contact:website'] || t.url || '';\n  const lat = el.lat != null ? el.lat : (el.center && el.center.lat);\n  const lon = el.lon != null ? el.lon : (el.center && el.center.lon);\n  const addressParts = [t['addr:housenumber'], t['addr:street'], t['addr:city'], t['addr:postcode']].filter(Boolean);\n  results.push({ json: {\n    name: t.name || '(unnamed)',\n    category: settings.category,\n    address: addressParts.join(', '),\n    phone: t.phone || t['contact:phone'] || '',\n    website: website,\n    website_on_file: website ? 'yes' : 'no',\n    opening_hours: t.opening_hours || '',\n    latitude: lat != null ? lat : '',\n    longitude: lon != null ? lon : '',\n    osm_link: 'https://www.openstreetmap.org/' + el.type + '/' + el.id,\n    search_location: settings.searchLocation,\n    search_radius_m: settings.radiusMeters,\n    retrieved_at: new Date().toISOString()\n  } });\n}\nreturn results.slice(0, max);"
      },
      "id": "366c09a2-1377-49db-a927-ee4d7a5bfa3c",
      "name": "Normalize businesses",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1200,
        368
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "id": "22a86f1f-ca3c-42f1-ad99-6ff233698477",
      "name": "Append to results sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1392,
        368
      ]
    },
    {
      "parameters": {},
      "id": "c613e46b-e687-4020-bf98-a3ea320bbc42",
      "name": "No matching location",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        976,
        736
      ]
    },
    {
      "parameters": {
        "content": "## Find nearby businesses by proximity using OpenStreetMap and save to Google Sheets\n\nLook up businesses around any location and record which ones list a website. Useful for local market research, building area directories, and territory mapping.\n\n### How it works\n1. Submit the form with a location, business category, search radius, and result limit.\n2. Nominatim geocodes the location to coordinates (1 request).\n3. A single Overpass API call returns every matching business in the radius, with their OpenStreetMap tags (website, phone, hours).\n4. Each business is normalized and appended to your Google Sheet, with a `website_on_file` yes/no flag.\n\n### Setup steps\n- [ ] Connect a Google Sheets credential and pick a destination sheet on \"Append to results sheet\".\n- [ ] Open the Settings node and put a real contact address in the User-Agent value (OpenStreetMap policy requires it).\n\n### Customization\n- Edit the category to OpenStreetMap tag map in **Build Overpass query**.\n- Adjust the radius options or add columns in **Normalize businesses**.\n- Filter your sheet to `website_on_file = no` to focus your research.",
        "height": 796,
        "width": 460
      },
      "id": "8f36ca5d-9d18-4173-9867-1d9e6c75003d",
      "name": "Sticky Note b4c0bf39",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -400,
        48
      ]
    },
    {
      "parameters": {
        "content": "## 1. Search input\nThe form collects location, category, radius and limit. Settings holds editable endpoints and your contact User-Agent.",
        "height": 348,
        "width": 372,
        "color": 7
      },
      "id": "5c532087-d014-4b0f-b266-b55102ad4f87",
      "name": "Sticky Note fd72b4dc",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        80,
        192
      ]
    },
    {
      "parameters": {
        "content": "## 2. Geocode + find businesses\nNominatim turns the location into coordinates; one Overpass call returns all matching businesses nearby.",
        "height": 360,
        "width": 668,
        "color": 7
      },
      "id": "857a7e1f-28ad-4754-9da8-6b52d8c5bb4a",
      "name": "Sticky Note bd8080c9",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        464,
        192
      ]
    },
    {
      "parameters": {
        "content": "## 3. Save to Google Sheets\nEach business becomes a row, flagged by whether it lists a website.",
        "height": 360,
        "width": 440,
        "color": 7
      },
      "id": "0174eca2-a1d6-47fc-a403-6df1010df424",
      "name": "Sticky Note 164f5c83",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1136,
        192
      ]
    },
    {
      "parameters": {
        "content": "## Respect the OpenStreetMap usage policy\nSet a real contact in the Settings User-Agent and keep volume modest (about 1 request per second). A missing website tag means \"worth checking\", not \"no site exists\".",
        "height": 196,
        "width": 396,
        "color": 3
      },
      "id": "4bb650a8-f65e-4f2d-8c56-65f43ea8025d",
      "name": "Sticky Note 35a4b80b",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        464,
        560
      ]
    },
    {
      "parameters": {
        "content": "## Handle no match\nIf the location cannot be geocoded, the IF false branch stops here, so no bad coordinates are queried.",
        "height": 318,
        "width": 300,
        "color": 7
      },
      "id": "b7f4a210-3c9d-4e61-9a52-1d0e7c884411",
      "name": "Sticky Note nomatch",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        864,
        560
      ]
    }
  ],
  "connections": {
    "Search request": {
      "main": [
        [
          {
            "node": "Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings": {
      "main": [
        [
          {
            "node": "Geocode location (Nominatim)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Geocode location (Nominatim)": {
      "main": [
        [
          {
            "node": "Location found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Location found?": {
      "main": [
        [
          {
            "node": "Build Overpass query",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No matching location",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Overpass query": {
      "main": [
        [
          {
            "node": "Find businesses (Overpass)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find businesses (Overpass)": {
      "main": [
        [
          {
            "node": "Normalize businesses",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize businesses": {
      "main": [
        [
          {
            "node": "Append to results sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "tags": [],
  "meta": {
    "templateCredsSetupCompleted": false
  }
}