Cómo analizar calles / direcciones postales de forma libre sin texto y en componentes

Hacemos negocios principalmente en los Estados Unidos y estamos tratando de mejorar la experiencia del usuario combinando todos los campos de direcciones en un solo área de texto. Pero hay algunos problemas:

  • La dirección que el usuario escribe puede no ser correcta o en un formato estándar
  • La dirección debe separarse en partes (calle, ciudad, estado, etc.) para procesar pagos con tarjeta de crédito
  • Los usuarios pueden ingresar más que solo su dirección (como su nombre o compañía con ella)
  • Google puede hacer esto, pero los Términos de servicio y los límites de consulta son prohibitivos, especialmente con un presupuesto ajustado.

Aparentemente, esta es una pregunta común:

  • Script PHP para analizar la dirección?
  • ¿Cómo puedo analizar la dirección de formato libre para guardarla en la base de datos?
  • analizador de direcciones postales de java
  • Forma más eficiente de extraer componentes de dirección
  • ¿Cómo puedo mostrar una dirección postal pre poblada en la pantalla de contactos con calle, ciudad, código postal de Android?
  • Dirección de PHP de regexp en los Estados Unidos

¿Hay alguna manera de aislar una dirección del texto que la rodea y dividirla en pedazos? ¿Hay una expresión regular para analizar las direcciones?

Vi esta pregunta mucho cuando trabajaba para una empresa de verificación de direcciones. Estoy publicando la respuesta aquí para que sea más accesible para los progtwigdores que buscan la misma pregunta. La compañía en la que trabajaba procesó miles de millones de direcciones, y aprendimos mucho en el proceso.

Primero, necesitamos entender algunas cosas sobre las direcciones.

Las direcciones no son regulares

Esto significa que las expresiones regulares están fuera. Lo he visto todo, desde simples expresiones regulares que coinciden con direcciones en un formato muy específico, hasta esto:

/ \ s + (\ d {2,5} \ s +) (?! [a | p] m \ b) (([a-zA-Z | \ s +] {1,5}) {1,2}) ? ([\ s | \, |.] +)? (([a-zA-Z | \ s +] {1,30}) {1,4}) (corte | ct | calle | st | unidad | dr | lane | ln | road | rd | blvd) ([\ s | \, |. | \;] +)? (([a-zA-Z | \ s +] {1,30}) {1,2} ) ([\ s | \, |.] +)? \ b (AK | AL | AR | AZ | CA | CO | CT | DC | DE | FL | GA | GU | HI | IA | ID | IL | IN | KS | KY | LA | MA | MD | ME | MI | MN | MO | MS | MT | NC | ND | NE | NH | NJ | NM | NV | NY | OH | OK | O | PA | RI | SC | SD | TN | TX | UT | VA | VI | VT | WA | WI | WV | WY) ([\ s | \, |.] +)? (\ S + \ d {5})? ([\ S | \, |.] +) / i

… a este, donde un archivo de clase de línea 900+ genera una expresión regular supermasiva sobre la marcha para que coincida aún más. No los recomiendo (por ejemplo, aquí hay un violín de la expresión regular anterior, que comete muchos errores ). No hay una fórmula mágica fácil para hacer que esto funcione. En teoría y por teoría, no es posible emparejar direcciones con una expresión regular.

La Publicación 28 de USPS documenta los muchos formatos de direcciones que son posibles, con todas sus palabras clave y variaciones. Lo peor de todo es que las direcciones son a menudo ambiguas. Las palabras pueden significar más de una cosa (“St” puede ser “Santo” o “Calle”) y hay palabras que estoy bastante seguro de que inventaron. (¿Quién sabía que “Stravenue” era un sufijo callejero?)

Necesitarías algún código que realmente entienda las direcciones, y si ese código existe, es un secreto comercial. Pero probablemente puedas enrollar el tuyo si realmente te gusta eso.

Las direcciones vienen en formas y tamaños inesperados

Estas son algunas direcciones inventadas (pero completas):

1) 102 main street Anytown, state 2) 400n 600e #2, 52173 3) po #104 60203 

Incluso estos son posiblemente válidos:

 4) 829 LKSDFJlkjsdflkjsdljf Bkpw 12345 5) 205 1105 14 90210 

Obviamente, estos no están estandarizados. La puntuación y los saltos de línea no están garantizados. Esto es lo que está pasando:

  1. El número 1 está completo porque contiene una dirección y una ciudad y un estado. Con esa información, hay suficiente para identificar la dirección, y puede considerarse “entregable” (con cierta estandarización).

  2. El número 2 está completo porque también contiene una dirección (con un número secundario / unidad) y un código postal de 5 dígitos, que es suficiente para identificar una dirección.

  3. El número 3 es un formato de buzón de correo completo, ya que contiene un código postal.

  4. El número 4 también está completo porque el código postal es único , lo que significa que una entidad privada o corporación ha comprado ese espacio de direcciones. Un código postal único es para espacios de entrega concentrados o de gran volumen. Cualquier cosa dirigida al código postal 12345 va a General Electric en Schenectady, NY. Este ejemplo no llegará a nadie en particular, pero USPS aún podría entregarlo.

  5. El número 5 también está completo, lo creas o no. Con solo esos números, se puede descubrir la dirección completa cuando se analiza en una base de datos de todas las direcciones posibles. Completar las direcciones, el designador secundario y el código ZIP + 4 faltantes es trivial cuando ves cada número como un componente. Esto es lo que parece, completamente expandido y estandarizado:

205 N 1105 W Apt 14

Beverly Hills CA 90210-5221

Los datos de dirección no son tuyos

En la mayoría de los países que brindan datos de direcciones oficiales a los proveedores con licencia, los datos de las direcciones pertenecen a la agencia de gobierno. En los EE. UU., El USPS posee las direcciones. Lo mismo es cierto para Canada Post, Royal Mail y otros, aunque cada país impone o define la propiedad de manera un poco diferente. Saber esto es importante, ya que generalmente prohíbe la ingeniería inversa de la base de datos de direcciones. Debe tener cuidado de cómo adquirir, almacenar y usar los datos.

Google Maps es un recurso común para solucionar rápidamente las direcciones, pero el TOS es bastante prohibitivo; por ejemplo, no puede usar sus datos o API sin mostrar un mapa de Google, y solo para fines no comerciales (a menos que pague), y no puede almacenar los datos (a excepción de la caché temporal). Tiene sentido. Los datos de Google son algunos de los mejores del mundo. Sin embargo, Google Maps no verifica la dirección. Si no existe una dirección, todavía le mostrará dónde estaría la dirección si existiera (pruébela en su propia calle, use un número de casa que sepa que no existe). Esto es útil a veces, pero ten en cuenta eso.

La política de uso de Nominatim es similarmente restrictiva , especialmente para el gran volumen y uso comercial, y los datos provienen principalmente de fonts gratuitas, por lo que no se mantiene bien (tal es la naturaleza de los proyectos abiertos); sin embargo, esto puede seguir siendo adecuado. tus necesidades. Es apoyado por una gran comunidad.

El USPS tiene una API, pero baja mucho y no tiene garantías ni soporte. También podría ser difícil de usar. Algunas personas lo usan con moderación sin problemas. Pero es fácil pasar por alto que el USPS requiere que use su API solo para confirmar las direcciones para enviar a través de ellos.

La gente espera que las direcciones sean difíciles

Desafortunadamente, hemos condicionado a nuestra sociedad a esperar que las direcciones sean complicadas. Hay docenas de buenos artículos de UX a través de Internet sobre esto, pero el hecho es que si tiene un formulario de dirección con campos individuales, eso es lo que los usuarios esperan, aunque dificulta las direcciones de caso extremo que no se ajustan a la el formato está esperando, o tal vez el formulario requiere un campo que no debería. O los usuarios no saben dónde colocar cierta parte de su dirección.

Podría seguir y seguir sobre el mal UX de formularios de pago en estos días, pero en su lugar solo diré que combinar las direcciones en un solo campo será un cambio bienvenido – las personas podrán escribir su dirección como mejor les parezca , en lugar de tratar de descubrir su forma larga. Sin embargo, este cambio será inesperado y los usuarios pueden encontrarlo un poco discordante al principio. Solo ten cuidado con eso.

Parte de este dolor se puede aliviar colocando el campo del país al frente, antes de la dirección. Cuando llenan primero el campo del país, usted sabe cómo hacer que su formulario aparezca. Tal vez tenga una buena manera de tratar con direcciones de EE.UU. de campo único, por lo que si seleccionan Estados Unidos, puede reducir su formulario a un solo campo, de lo contrario, mostrar los campos de componentes. ¡Solo cosas para pensar!

Ahora sabemos por qué es difícil; ¿Qué puedes hacer al respecto?

El USPS otorga licencias a los proveedores a través de un proceso llamado CASS ™ Certification para proporcionar direcciones verificadas a los clientes. Estos proveedores tienen acceso a la base de datos de USPS, actualizada mensualmente. Su software debe cumplir con los estándares rigurosos para ser certificado, y no suelen requerir el acuerdo de los términos limitativos antes mencionados.

Hay muchas compañías certificadas por CASS que pueden procesar listas o tener API: Melissa Data, Experian QAS y SmartyStreets por nombrar algunas.

(Debido a que recibí críticas por “publicidad”, trunqué mi respuesta en este punto. Depende de usted encontrar una solución que funcione para usted).

La Verdad: Realmente, amigos, no trabajo en ninguna de estas compañías. No es un anuncio.

Hay muchos analizadores de direcciones de calles. Vienen en dos sabores básicos: los que tienen bases de datos de nombres de lugares y calles, y otros que no.

Un analizador de direcciones de calles de expresión regular puede obtener hasta una tasa de éxito del 95% sin muchos problemas. Luego comienzas a golpear los casos inusuales. El de Perl en CPAN, “Geo :: StreetAddress :: US”, es bastante bueno. Hay puertos de Python y Javascript de eso, todos de código abierto. Tengo una versión mejorada en Python que mueve ligeramente la tasa de éxito al manejar más casos. Sin embargo, para obtener el último 3% correcto, necesita bases de datos para ayudar con la desambiguación.

Una base de datos con códigos postales de 3 dígitos y nombres y abreviaturas de estados de EE. UU. Es de gran ayuda. Cuando un analizador ve un código postal y un nombre de estado consistentes, puede comenzar a vincularse al formato. Esto funciona muy bien para los Estados Unidos y el Reino Unido.

El análisis correcto de la dirección de la calle comienza desde el final y funciona hacia atrás. Así es como lo hacen los sistemas de USPS. Las direcciones son menos ambiguas al final, donde los nombres de los países, las ciudades y los códigos postales son relativamente fáciles de reconocer. Los nombres de las calles generalmente se pueden aislar. Las ubicaciones en las calles son las más complejas de analizar; allí encuentras cosas como “Fifth Floor” y “Staples Pavillion”. Ahí es cuando una base de datos es de gran ayuda.

libpostal: una biblioteca de código abierto para analizar direcciones, entrenamiento con datos de OpenStreetMap, OpenAddresses y OpenCage.

https://github.com/openvenues/libpostal ( más información al respecto )

Otras herramientas / servicios:

ACTUALIZACIÓN: Geocode.xyz ahora funciona en todo el mundo. Para ver ejemplos, consulte https://geocode.xyz

Para EE. UU., México y Canadá, consulte geocoder.ca .

Por ejemplo:

Entrada: algo pasando cerca de la intersección de main y arthur kill rd new york

Salida:

  40.5123510000 -74.2500500000 347,718 America/New_York  main arthur kill   STATEN ISLAND NY 11385 0.9   

También puede verificar los resultados en la interfaz web u obtener resultados como Json o Jsonp. p.ej. Estoy buscando restaurantes alrededor de 123 Main Street, Nueva York

¿Sin código? ¡Para vergüenza!

Aquí hay un analizador de direcciones de JavaScript simple. Es bastante horrible por cada una de las razones que Matt da en su disertación anterior (con la que estoy casi 100% de acuerdo: las direcciones son tipos complejos, y los humanos cometen errores; mejor subcontratar y automatizar esto, cuando puede).

Pero en lugar de llorar, decidí intentar:

Este código funciona bien para analizar la mayoría de los resultados de Esri para findAddressCandidate y también con algunos otros geocodificadores (inversos) que devuelven una sola línea de dirección donde la calle / ciudad / estado están delimitados por comas. Puede extender si quiere o escribir analizadores específicos del país. O simplemente use esto como estudio de caso de lo desafiante que puede ser este ejercicio o de lo mal que estoy en JavaScript. Admito que solo pasé unos treinta minutos (las iteraciones futuras podrían agregar cachés, validación zip y búsquedas de estado, así como el contexto de ubicación del usuario), pero funcionó para mi caso de uso: el usuario final ve un formulario que analiza la respuesta de búsqueda del código geográfico en 4 cajas de texto. Si el análisis de direcciones sale mal (lo cual es raro a menos que los datos de origen fueran pobres) no es gran cosa: ¡el usuario puede verificarlo y solucionarlo! (Pero para las soluciones automatizadas, se puede descartar / ignorar o marcar como error, por lo que dev puede admitir el nuevo formato o corregir los datos de origen).

 /* address assumptions: - US addresses only (probably want separate parser for different countries) - No country code expected. - if last token is a number it is probably a postal code -- 5 digit number means more likely - if last token is a hyphenated string it might be a postal code -- if both sides are numeric, and in form #####-#### it is more likely - if city is supplied, state will also be supplied (city names not unique) - zip/postal code may be omitted even if has city & state - state may be two-char code or may be full state name. - commas: -- last comma is usually city/state separator -- second-to-last comma is possibly street/city separator -- other commas are building-specific stuff that I don't care about right now. - token count: -- because units, street names, and city names may contain spaces token count highly variable. -- simplest address has at least two tokens: 714 OAK -- common simple address has at least four tokens: 714 S OAK ST -- common full (mailing) address has at least 5-7: --- 714 OAK, RUMTOWN, VA 59201 --- 714 S OAK ST, RUMTOWN, VA 59201 -- complex address may have a dozen or more: --- MAGICICIAN SUPPLY, LLC, UNIT 213A, MAGIC TOWN MALL, 13 MAGIC CIRCLE DRIVE, LAND OF MAGIC, MA 73122-3412 */ var rawtext = $("textarea").val(); var rawlist = rawtext.split("\n"); function ParseAddressEsri(singleLineaddressString) { var address = { street: "", city: "", state: "", postalCode: "" }; // tokenize by space (retain commas in tokens) var tokens = singleLineaddressString.split(/[\s]+/); var tokenCount = tokens.length; var lastToken = tokens.pop(); if ( // if numeric assume postal code (ignore length, for now) !isNaN(lastToken) || // if hyphenated assume long zip code, ignore whether numeric, for now lastToken.split("-").length - 1 === 1) { address.postalCode = lastToken; lastToken = tokens.pop(); } if (lastToken && isNaN(lastToken)) { if (address.postalCode.length && lastToken.length === 2) { // assume state/province code ONLY if had postal code // otherwise it could be a simple address like "714 S OAK ST" // where "ST" for "street" looks like two-letter state code // possibly this could be resolved with registry of known state codes, but meh. (and may collide anyway) address.state = lastToken; lastToken = tokens.pop(); } if (address.state.length === 0) { // check for special case: might have State name instead of State Code. var stateNameParts = [lastToken.endsWith(",") ? lastToken.substring(0, lastToken.length - 1) : lastToken]; // check remaining tokens from right-to-left for the first comma while (2 + 2 != 5) { lastToken = tokens.pop(); if (!lastToken) break; else if (lastToken.endsWith(",")) { // found separator, ignore stuff on left side tokens.push(lastToken); // put it back break; } else { stateNameParts.unshift(lastToken); } } address.state = stateNameParts.join(' '); lastToken = tokens.pop(); } } if (lastToken) { // here is where it gets trickier: if (address.state.length) { // if there is a state, then assume there is also a city and street. // PROBLEM: city may be multiple words (spaces) // but we can pretty safely assume next-from-last token is at least PART of the city name // most cities are single-name. It would be very helpful if we knew more context, like // the name of the city user is in. But ignore that for now. // ideally would have zip code service or lookup to give city name for the zip code. var cityNameParts = [lastToken.endsWith(",") ? lastToken.substring(0, lastToken.length - 1) : lastToken]; // assumption / RULE: street and city must have comma delimiter // addresses that do not follow this rule will be wrong only if city has space // but don't care because Esri formats put comma before City var streetNameParts = []; // check remaining tokens from right-to-left for the first comma while (2 + 2 != 5) { lastToken = tokens.pop(); if (!lastToken) break; else if (lastToken.endsWith(",")) { // found end of street address (may include building, etc. - don't care right now) // add token back to end, but remove trailing comma (it did its job) tokens.push(lastToken.endsWith(",") ? lastToken.substring(0, lastToken.length - 1) : lastToken); streetNameParts = tokens; break; } else { cityNameParts.unshift(lastToken); } } address.city = cityNameParts.join(' '); address.street = streetNameParts.join(' '); } else { // if there is NO state, then assume there is NO city also, just street! (easy) // reasoning: city names are not very original (Portland, OR and Portland, ME) so if user wants city they need to store state also (but if you are only ever in Portlan, OR, you don't care about city/state) // put last token back in list, then rejoin on space tokens.push(lastToken); address.street = tokens.join(' '); } } // when parsing right-to-left hard to know if street only vs street + city/state // hack fix for now is to shift stuff around. // assumption/requirement: will always have at least street part; you will never just get "city, state" // could possibly tweak this with options or more intelligent parsing&sniffing if (!address.city && address.state) { address.city = address.state; address.state = ''; } if (!address.street) { address.street = address.city; address.city = ''; } return address; } // get list of objects with discrete address properties var addresses = rawlist .filter(function(o) { return o.length > 0 }) .map(ParseAddressEsri); $("#output").text(JSON.stringify(addresses)); console.log(addresses); 
   

En uno de nuestros proyectos, hemos utilizado el siguiente analizador de direcciones. Analiza direcciones para la mayoría de los países del mundo con buena precisión.

http://address-parser.net/

Está disponible como biblioteca independiente o como API en vivo.

Si desea confiar en los datos de OSM, libpostal es muy potente y maneja muchas de las advertencias más comunes con las entradas de dirección.