Run the Geocoder in Your Browser — Next Steps (API + UI)

You’ve got the API running:

Running plumber API at http://127.0.0.1:8787
Running swagger Docs at http://127.0.0.1:8787/__docs__/

Below are two easy ways to use it from a browser and upload a dataset.


A) Fastest Path — Use Swagger UI (built in)

Where: Open this in your browser → http://127.0.0.1:8787/__docs__/

  1. In the list of endpoints, click POST /geocode_csv.
  2. Click Try it out.
  3. In address_col, enter the column name in your CSV that holds addresses (e.g., address).
  4. Click Choose file and pick your CSV.
  5. Click Execute → You’ll get geocoded JSON back in the response panel.

Tip: To create a 70:30 split right in Swagger, scroll to POST /split, click Try it out, and paste a body like this (use your actual geocode results):

{
  "ratio": 0.7,
  "data": [
    {"address":"1 Apple Park Way, Cupertino, CA","lat":37.3349,"long":-122.0090,"display_name":"...","status":"ok","message":null},
    {"address":"350 Fifth Ave, New York, NY","lat":40.7484,"long":-73.9857,"display_name":"...","status":"ok","message":null}
  ]
}

B) Simple Upload Page (no build tools)

Where: Save the file below as /Users/dah512/geocode_upload.html, then double‑click to open it in your browser. It will:

  • Upload a CSV to the API (/geocode_csv),
  • Show results in a table,
  • Request a 70:30 split from the API and download the CSVs.
<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <title>Geocode Uploader (R plumber)</title>
  <style>
    body { font-family: system-ui, -apple-system, sans-serif; max-width: 960px; margin: 2rem auto; padding: 0 1rem; }
    table { border-collapse: collapse; width: 100%; margin-top: 1rem; }
    th, td { border: 1px solid #ccc; padding: 6px 8px; text-align: left; }
    .actions { display:flex; gap:8px; margin-top: 12px; }
  </style>
</head>
<body>
  <h1>Geocode CSV → 70:30 Split</h1>
  <p>API must be running at <code>http://127.0.0.1:8787</code>. Choose a CSV and set the address column name.</p>
  <div>
    <input id="file" type="file" accept=".csv" />
    <input id="col" type="text" value="address" />
    <button id="go">Geocode</button>
  </div>
  <div class="actions">
    <button id="split" disabled>Export 70:30 Split</button>
  </div>
  <div id="status"></div>
  <div style="overflow-x:auto">
    <table id="tbl" style="display:none"></table>
  </div>
<script>
const API = '';
let lastRows = null;

function msg(s){ document.getElementById('status').textContent = s; }

function toCSV(items){
  if(!items || !items.length) return '';
  const headers = Object.keys(items[0]);
  const lines = [headers.join(',')];
  for(const row of items){
    const vals = headers.map(h=>{
      const v = row[h]==null? '' : String(row[h]).replace(/"/g,'""');
      return /[",\n]/.test(v) ? `"${v}"` : v;
    });
    lines.push(vals.join(','));
  }
  return lines.join('\n');
}

function download(csv, name){
  const blob = new Blob([csv], {type:'text/csv;charset=utf-8;'});
  const a = document.createElement('a');
  a.href = URL.createObjectURL(blob);
  a.download = name; a.click(); URL.revokeObjectURL(a.href);
}

document.getElementById('go').onclick = async () => {
  const f = document.getElementById('file').files[0];
  const col = document.getElementById('col').value.trim();
  if(!f){ return msg('Choose a CSV first.'); }
  if(!col){ return msg('Enter the address column name.'); }
  const fd = new FormData(); fd.append('file', f);
  msg('Uploading & geocoding…');
  try{
    const r = await fetch(`${API}/geocode_csv?address_col=${encodeURIComponent(col)}`, { method:'POST', body: fd });
    const data = await r.json();
    if(data.error){ msg('Error: '+data.error); return; }
    lastRows = data;
    // Render table
    const tbl = document.getElementById('tbl');
    const keys = Object.keys(data[0]||{});
    tbl.innerHTML = '';
    if(keys.length){
      const thead = document.createElement('thead');
      const trh = document.createElement('tr');
      keys.forEach(k=>{ const th=document.createElement('th'); th.textContent=k; trh.appendChild(th); });
      thead.appendChild(trh); tbl.appendChild(thead);
      const tbody = document.createElement('tbody');
      data.forEach(row=>{
        const tr = document.createElement('tr');
        keys.forEach(k=>{ const td=document.createElement('td'); td.textContent = row[k]==null? '' : String(row[k]); tr.appendChild(td); });
        tbody.appendChild(tr);
      });
      tbl.appendChild(tbody); tbl.style.display='table';
    }
    msg(`Rows: ${data.length}`);
    document.getElementById('split').disabled = !data.length;
  }catch(e){ console.error(e); msg('Request failed. Is the API running?'); }
};

document.getElementById('split').onclick = async () => {
  if(!lastRows){ return; }
  msg('Requesting 70:30 split…');
  try{
    const r = await fetch(`${API}/split`, {
      method: 'POST', headers: {'Content-Type':'application/json'},
      body: JSON.stringify({ ratio: 0.7, data: lastRows })
    });
    const data = await r.json();
    if(data.error){ msg('Split error: '+data.error); return; }
    const trainCsv = toCSV(data.train||[]);
    const testCsv  = toCSV(data.test||[]);
    if(trainCsv) download(trainCsv, 'geocode_train_70.csv');
    if(testCsv)  download(testCsv,  'geocode_test_30.csv');
    msg('Download complete.');
  }catch(e){ console.error(e); msg('Split request failed.'); }
};
</script>
</body>
</html>

Steps:

  1. Start the API if it’s not already running:
Rscript -e 'pr <- plumber::plumb("~/R-geocode-smoketest/api.R"); pr$run(host="127.0.0.1", port=8787)'
  1. Open /Users/dah512/geocode_upload.html in your browser.
  2. Pick your CSV + address column name → GeocodeExport 70:30 Split.

(Optional) React UI — add CSV Upload button

If you prefer your React app, add a file input and post to the API using FormData. Your Vite proxy should map /apihttp://127.0.0.1:8787.

vite.config.ts (dev proxy)

server: { proxy: { '/api': { target: 'http://127.0.0.1:8787', changeOrigin: true, rewrite: p => p.replace(/^\/api/, '') } } }

src/App.tsx (add upload)

const [rows, setRows] = useState<any[]|null>(null);
async function uploadCsv(file: File, addressCol: string) {
  const fd = new FormData(); fd.append('file', file);
  const r = await fetch(`/api/geocode_csv?address_col=${encodeURIComponent(addressCol)}`, { method: 'POST', body: fd });
  const data = await r.json(); setRows(data.error? [] : data);
}
async function split70() {
  if (!rows) return;
  const r = await fetch('/api/split', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ ratio: 0.7, data: rows })});
  const data = await r.json(); /* download CSVs as needed */
}

UI snippet:

<input type="file" accept=".csv" onChange={e => e.target.files && uploadCsv(e.target.files[0], 'address')} />
<button onClick={split70} disabled={!rows}>Export 70:30</button>

Troubleshooting

  • All NA / errors: You may have hit Nominatim rate limits or a column mismatch; confirm address_col exactly matches your CSV header.
  • CORS: Dev is permissive in the API and Vite proxy avoids CORS.
  • File path spaces: If testing with curl, quote paths: -F 'file=@"/Users/dah512/My File.csv"'.
  • Run location: The API runs from ~/R-geocode-smoketest/api.R, but you can launch it from any directory; it still listens on 127.0.0.1:8787.