Extract geocoding functions into shared api/inc/geocode.cfm and call geocodeAddressById() via cfthread after every address INSERT or UPDATE. Uses OpenStreetMap Nominatim (free, no API key). Non-blocking — the HTTP call runs in a background thread so responses aren't delayed. Affected endpoints: - setup/saveWizard.cfm (new business creation) - businesses/update.cfm (business address update) - portal/updateSettings.cfm (portal settings save) - addresses/add.cfm (customer delivery address) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
160 lines
5.6 KiB
Text
160 lines
5.6 KiB
Text
<cfsetting showdebugoutput="false">
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Address Geocoding</title>
|
|
<style>
|
|
body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; max-width: 1000px; margin: 40px auto; padding: 20px; background: ##1a1a1a; color: ##fff; }
|
|
h1 { color: ##4CAF50; }
|
|
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
|
|
th, td { padding: 10px; text-align: left; border-bottom: 1px solid ##333; }
|
|
th { background: ##2a2a2a; }
|
|
tr:hover { background: ##2a2a2a; }
|
|
.address { color: ##aaa; font-size: 13px; }
|
|
.success { color: ##4CAF50; padding: 10px; background: ##1b3d1b; border-radius: 4px; margin: 10px 0; }
|
|
.error { color: ##f44336; padding: 10px; background: ##3d1b1b; border-radius: 4px; margin: 10px 0; }
|
|
.has-coords { color: ##4CAF50; }
|
|
.no-coords { color: ##ff9800; }
|
|
button { padding: 6px 12px; background: ##4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; }
|
|
button:hover { background: ##45a049; }
|
|
.btn-lookup { background: ##2196F3; }
|
|
.btn-lookup:hover { background: ##1976D2; }
|
|
.coords { font-family: monospace; font-size: 12px; color: ##888; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>Address Geocoding</h1>
|
|
<p>Auto-geocode addresses using OpenStreetMap Nominatim (free, no API key required)</p>
|
|
|
|
<cfinclude template="/api/inc/geocode.cfm">
|
|
|
|
<cfif structKeyExists(url, "geocode") AND structKeyExists(url, "addressId")>
|
|
<cfset addressId = val(url.addressId)>
|
|
<cfquery name="addr" datasource="payfrit">
|
|
SELECT Line1, Line2, City, ZIPCode
|
|
FROM Addresses WHERE ID = <cfqueryparam value="#addressId#" cfsqltype="cf_sql_integer">
|
|
</cfquery>
|
|
<cfif addr.recordCount GT 0>
|
|
<cfset fullAddress = buildAddressString(addr.Line1, addr.Line2, addr.City, addr.ZIPCode)>
|
|
<cfset geo = geocodeAddress(fullAddress)>
|
|
<cfif geo.success>
|
|
<cfquery datasource="payfrit">
|
|
UPDATE Addresses SET Lat = <cfqueryparam value="#geo.lat#" cfsqltype="cf_sql_decimal">,
|
|
Lng = <cfqueryparam value="#geo.lng#" cfsqltype="cf_sql_decimal">
|
|
WHERE ID = <cfqueryparam value="#addressId#" cfsqltype="cf_sql_integer">
|
|
</cfquery>
|
|
<cfoutput><div class="success">Geocoded Address ID #addressId#: #geo.lat#, #geo.lng#</div></cfoutput>
|
|
<cfelse>
|
|
<cfoutput><div class="error">Failed: #geo.error#</div></cfoutput>
|
|
</cfif>
|
|
</cfif>
|
|
</cfif>
|
|
|
|
<cfif structKeyExists(url, "geocodeAll")>
|
|
<cfquery name="missing" datasource="payfrit">
|
|
SELECT ID, Line1, Line2, City, ZIPCode
|
|
FROM Addresses
|
|
WHERE (Lat IS NULL OR Lat = 0)
|
|
AND Line1 IS NOT NULL AND Line1 != ''
|
|
</cfquery>
|
|
<cfset successCount = 0>
|
|
<cfset failCount = 0>
|
|
<cfloop query="missing">
|
|
<cfset fullAddress = buildAddressString(missing.Line1, missing.Line2, missing.City, missing.ZIPCode)>
|
|
<cfset geo = geocodeAddress(fullAddress)>
|
|
<cfif geo.success>
|
|
<cfquery datasource="payfrit">
|
|
UPDATE Addresses SET Lat = <cfqueryparam value="#geo.lat#" cfsqltype="cf_sql_decimal">,
|
|
Lng = <cfqueryparam value="#geo.lng#" cfsqltype="cf_sql_decimal">
|
|
WHERE ID = <cfqueryparam value="#missing.ID#" cfsqltype="cf_sql_integer">
|
|
</cfquery>
|
|
<cfset successCount = successCount + 1>
|
|
<cfelse>
|
|
<cfset failCount = failCount + 1>
|
|
</cfif>
|
|
<cfset sleep(1100)>
|
|
</cfloop>
|
|
<cfoutput><div class="success">Geocoded #successCount# addresses. #failCount# failed.</div></cfoutput>
|
|
</cfif>
|
|
|
|
<cfquery name="addresses" datasource="payfrit">
|
|
SELECT
|
|
b.ID AS BusinessID,
|
|
b.Name,
|
|
a.ID AS AddressID,
|
|
a.Line1,
|
|
a.Line2,
|
|
a.City,
|
|
a.ZIPCode,
|
|
a.Lat AS Latitude,
|
|
a.Lng AS Longitude
|
|
FROM Businesses b
|
|
LEFT JOIN Addresses a ON b.AddressID = a.ID
|
|
ORDER BY b.Name
|
|
</cfquery>
|
|
|
|
<cfset missingCount = 0>
|
|
<cfloop query="addresses">
|
|
<cfif (NOT len(addresses.Latitude) OR val(addresses.Latitude) EQ 0) AND len(addresses.Line1)>
|
|
<cfset missingCount = missingCount + 1>
|
|
</cfif>
|
|
</cfloop>
|
|
|
|
<cfoutput>
|
|
<p>
|
|
<strong>#missingCount#</strong> addresses missing coordinates.
|
|
<cfif missingCount GT 0>
|
|
<a href="?geocodeAll=1"><button class="btn-lookup">Geocode All Missing (#missingCount#)</button></a>
|
|
<small style="color:##888;">(~#missingCount# seconds due to rate limiting)</small>
|
|
</cfif>
|
|
</p>
|
|
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Business</th>
|
|
<th>Address</th>
|
|
<th>Coordinates</th>
|
|
<th>Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<cfloop query="addresses">
|
|
<tr>
|
|
<td>
|
|
#addresses.Name#
|
|
<cfif len(addresses.Latitude) AND val(addresses.Latitude) NEQ 0>
|
|
<span class="has-coords">#chr(10003)#</span>
|
|
<cfelseif len(addresses.Line1)>
|
|
<span class="no-coords">#chr(9679)#</span>
|
|
</cfif>
|
|
</td>
|
|
<td class="address">
|
|
<cfif len(addresses.Line1)>
|
|
#addresses.Line1#<cfif len(addresses.Line2)>, #addresses.Line2#</cfif><br>
|
|
#addresses.City# #addresses.ZIPCode#
|
|
<cfelse>
|
|
<em style="color:##666;">No address</em>
|
|
</cfif>
|
|
</td>
|
|
<td class="coords">
|
|
<cfif len(addresses.Latitude) AND val(addresses.Latitude) NEQ 0>
|
|
#numberFormat(addresses.Latitude, "_.______")#<br>
|
|
#numberFormat(addresses.Longitude, "_.______")#
|
|
<cfelse>
|
|
-
|
|
</cfif>
|
|
</td>
|
|
<td>
|
|
<cfif len(addresses.Line1) AND len(addresses.AddressID)>
|
|
<a href="?geocode=1&addressId=#addresses.AddressID#"><button class="btn-lookup">Lookup</button></a>
|
|
</cfif>
|
|
</td>
|
|
</tr>
|
|
</cfloop>
|
|
</tbody>
|
|
</table>
|
|
</cfoutput>
|
|
|
|
</body>
|
|
</html>
|