¿Qué algoritmos calculan las direcciones del punto A al punto B en un mapa?


543

¿Cómo sugieren las direcciones los proveedores de mapas (como Google o Yahoo! Maps)?

Quiero decir, probablemente tienen datos del mundo real de alguna forma, que ciertamente incluyen distancias, pero también tal vez cosas como velocidades de conducción, presencia de aceras, horarios de trenes, etc. Pero supongamos que los datos están en un formato más simple, digamos un gráfico dirigido muy grande. con pesos de borde que reflejan distancias. Quiero poder calcular rápidamente las direcciones de un punto arbitrario a otro. A veces, estos puntos estarán muy juntos (dentro de una ciudad), mientras que a veces estarán muy separados (a campo traviesa).

Los algoritmos gráficos como el algoritmo de Dijkstra no funcionarán porque el gráfico es enorme. Afortunadamente, los algoritmos heurísticos como A * probablemente funcionarán. Sin embargo, nuestros datos están muy estructurados, ¿y quizás algún tipo de enfoque escalonado podría funcionar? (Por ejemplo, almacene direcciones precalculadas entre ciertos puntos "clave" muy separados, así como algunas direcciones locales. Luego, las direcciones para dos puntos lejanos incluirán direcciones locales a puntos clave, direcciones globales a otro punto clave y luego local direcciones de nuevo.)

¿Qué algoritmos se usan realmente en la práctica?

PD. Esta pregunta fue motivada por encontrar peculiaridades en las instrucciones de mapeo en línea. Contrariamente a la desigualdad del triángulo, a veces Google Maps piensa que XZ lleva más tiempo y es más lejos que usar un punto intermedio como en XYZ . ¿Pero quizás sus indicaciones para caminar también se optimicen para otro parámetro?

PPS Aquí hay otra violación de la desigualdad del triángulo que sugiere (para mí) que usan algún tipo de enfoque escalonado: XZ versus XYZ . El primero parece usar el prominente Boulevard de Sebastopol, aunque está un poco alejado.

Editar : Ninguno de estos ejemplos parece funcionar más, pero ambos lo hicieron en el momento de la publicación original.


3
Por cierto, el algoritmo A * "es una generalización del algoritmo de Dijkstra que reduce el tamaño del subgráfico que debe explorarse, si hay información adicional disponible que proporcione un límite inferior en la" distancia "al objetivo"
Mitch Wheat

Re A *: sí, de hecho. Afortunadamente, en nuestro caso, esta "información adicional" está disponible, por ejemplo, utilizando la distancia en línea recta. Cuando digo "Dijkstra" arriba, me refiero a Dijkstra vainilla.
A. Rex

Direcciones a pie? No sé de ningún otro lugar, pero por aquí (Hampshire, Reino Unido), la gran G no tiene datos de peatones: me encamina a lo largo de las carreteras alrededor de los recintos peatonales, etc. Lo único para lo que sirve es cambiar la estimación del tiempo que toma la ruta :)
jTresidder

No me importa particularmente si las instrucciones son para conducir o caminar. ¡Solo quiero saber cómo funcionan! La razón por la que tengo enlaces a pie allí es porque estaba calculando una manera de caminar por París y ver las 66 fuentes de Wallace. (Los puntos finales de esos mapas deben ser fuentes de Wallace.)
A. Rex

La recompensa por esta pregunta es alentar más y mejores respuestas, particularmente de personas que trabajan en uno de los principales proveedores. Se agradecen los comentarios sobre estructuras de datos, algoritmos, cuánto se calcula previamente, etc.
A. Rex

Respuestas:


526

Hablando como alguien que pasó 18 meses trabajando en una empresa de mapeo, que incluyó trabajar en el algoritmo de enrutamiento ... sí, Dijkstra funciona, con un par de modificaciones:

  • En lugar de hacer Dijkstra's una vez desde el origen hasta el destino, comienza en cada extremo y expande ambos lados hasta que se encuentren en el medio. Esto elimina aproximadamente la mitad del trabajo (2 * pi * (r / 2) ^ 2 vs pi * r ^ 2).
  • Para evitar explorar los callejones de cada ciudad entre su origen y destino, puede tener varias capas de datos de mapas: una capa de 'autopistas' que contiene solo autopistas, una capa 'secundaria' que contiene solo calles secundarias, y así sucesivamente. Luego, explora solo secciones más pequeñas de las capas más detalladas, expandiéndose según sea necesario. Obviamente, esta descripción deja de lado muchos detalles, pero se entiende la idea.

Con modificaciones a lo largo de esas líneas, puede hacer incluso enrutamiento entre países en un plazo muy razonable.


29
Alguien que trabajó en esto en el mundo real, ¡increíble! ¿Tiene alguna idea de cuánto es posible calcular previamente, como en el artículo sobre Google mencionado en otro comentario?
A. Rex

10
No hicimos ningún preprocesamiento de esa naturaleza, pero ciertamente parece una optimización interesante.
Nick Johnson el

29
"solo se garantiza producir una solución, no necesariamente la más eficiente" Esto no es cierto; Mientras la heurística utilizada sea admisible, el algoritmo A * produce la ruta de menor costo. Admisible significa que el costo nunca se sobreestima, pero puede subestimarse (pero las estimaciones pobres retrasarán el algoritmo). Es probable que el uso de datos del preprocesamiento ayude a mejorar la heurística admisible y, por lo tanto, a que A * funcione más rápido.
a1kmm el

66
En realidad, en consideración adicional, tienes toda la razón. Puede mejorar un algoritmo existente para hacer uso de esto simplemente agregando la Gran Distancia del Círculo entre el nodo objetivo y el destino al costo del borde que se está evaluando. En realidad, no estoy seguro de si nuestro algoritmo hizo eso, pero es una optimización muy sensata.
Nick Johnson el

11
A *, en el peor de los casos (una heurística que dice que todas las rutas son equivalentes), es exactamente igual a la de Djikstra.
Tordek

111

Esta pregunta ha sido un área activa de investigación en los últimos años. La idea principal es hacer un preprocesamiento en el gráfico una vez , para acelerar todas las consultas siguientes . Con esta información adicional, los itinerarios se pueden calcular muy rápido. Aún así, el algoritmo de Dijkstra es la base de todas las optimizaciones.

Arácnido describió el uso de búsqueda bidireccional y poda de bordes basada en información jerárquica. Estas técnicas de aceleración funcionan bastante bien, pero los algoritmos más recientes superan a estas técnicas por todos los medios. Con los algoritmos actuales, se pueden calcular las rutas más cortas en un tiempo considerablemente menor que un milisegundo en una red de carreteras continental. Una implementación rápida del algoritmo no modificado de Dijkstra necesita aproximadamente 10 segundos .

El artículo Algoritmos de planificación de ruta rápida de ingeniería ofrece una visión general del progreso de la investigación en ese campo. Consulte las referencias de ese documento para obtener más información.

Los algoritmos más rápidos conocidos no utilizan información sobre el estado jerárquico de la carretera en los datos, es decir, si se trata de una carretera o una carretera local. En cambio, calculan en un paso de preprocesamiento una jerarquía propia que se optimizó para acelerar la planificación de rutas. Esta precomputación se puede utilizar para podar la búsqueda: lejos del inicio y del destino, no es necesario tener en cuenta las carreteras lentas durante el algoritmo de Dijkstra. Los beneficios son un muy buen rendimiento y una garantía de corrección para el resultado.

Los primeros algoritmos optimizados de planificación de rutas solo se ocuparon de redes de carreteras estáticas, lo que significa que un borde en el gráfico tiene un valor de costo fijo. Esto no es cierto en la práctica, ya que queremos tener en cuenta información dinámica como atascos de tráfico o restricciones dependientes del vehículo. Los algoritmos más recientes también pueden resolver estos problemas, pero aún quedan problemas por resolver y la investigación continúa.

Si necesita las distancias de ruta más cortas para calcular una solución para el TSP , entonces probablemente esté interesado en matrices que contengan todas las distancias entre sus fuentes y destinos. Para esto, podría considerar calcular rutas más cortas de muchos a muchos utilizando jerarquías de carreteras . Tenga en cuenta que esto ha sido mejorado por nuevos enfoques en los últimos 2 años.


1
Iluminante, de hecho. ¿Cuáles son los enfoques más nuevos que está mencionando?
Tomas Pajonk

1
En particular las jerarquías de contracción. Puede encontrar más información al respecto aquí: algo2.iti.kit.edu/routeplanning.php . También hay una charla sobre tecnología de Google al respecto: youtube.com/watch?v=-0ErpE8tQbw
SebastianK

19

Solo abordando las violaciones de la desigualdad del triángulo, con suerte el factor adicional para el que están optimizando es el sentido común. No necesariamente quieres la ruta más corta o más rápida, ya que puede conducir al caos y la destrucción. . Si desea que sus instrucciones prefieran las rutas principales que son amigables para los camiones y pueden hacer frente a que se les envíe a todos los conductores que siguen la navegación por satélite, descarte rápidamente la desigualdad del triángulo [1].

Si Y es una calle residencial estrecha entre X y Z, probablemente solo desee usar el acceso directo a través de Y si el usuario solicita explícitamente XYZ. Si solicitan XZ, deben apegarse a las carreteras principales, incluso si está un poco más lejos y tarda un poco más. Es similar a la paradoja de Braess : si todos intentan tomar la ruta más corta y rápida, la congestión resultante significa que ya no es la ruta más rápida para nadie. Desde aquí nos desviamos de la teoría de grafos a la teoría de juegos.

[1] De hecho, cualquier esperanza de que las distancias producidas sean una función de distancia en el sentido matemático muere cuando se permiten carreteras de un solo sentido y se pierde el requisito de simetría. Perder la desigualdad del triángulo también es solo frotar sal en la herida.


14

Aquí están los algoritmos de enrutamiento más rápidos del mundo comparados y probados para su corrección:

http://algo2.iti.uka.de/schultes/hwy/schultes_diss.pdf

Aquí hay una charla técnica de Google sobre el tema:

http://www.youtube.com/watch?v=-0ErpE8tQbw

Aquí hay una implementación del algoritmo de jerarquías de carreteras como lo discutió schultes (actualmente solo en Berlín, estoy escribiendo la interfaz y también se está desarrollando una versión móvil):

http://tom.mapsforge.org/


9

No he trabajado en Google, Microsoft o Yahoo Maps antes, así que no puedo decirte cómo funcionan.

Sin embargo, diseñé un sistema de optimización de la cadena de suministro personalizado para una compañía de energía que incluía una aplicación de programación y enrutamiento para su flota de camiones. Sin embargo, nuestros criterios de enrutamiento fueron mucho más específicos del negocio que donde la construcción o el tráfico se ralentizan o el cierre de carriles.

Empleamos una técnica llamada ACO (optimización de colonias de hormigas) para programar y enrutar camiones. Esta técnica es una técnica de IA que se aplicó al problema del vendedor ambulante para resolver problemas de enrutamiento. El truco con ACO es construir un cálculo de error basado en hechos conocidos de la ruta para que el modelo de resolución de gráficos sepa cuándo abandonar (cuándo el error es lo suficientemente pequeño).

Puede buscar en Google ACO o TSP para obtener más información sobre esta técnica. Sin embargo, no he usado ninguna de las herramientas de IA de código abierto para esto, así que no puedo sugerir una (aunque escuché que SWARM era bastante completo).


44
enrutamiento! = cdta. en tsp, conoce todas las distancias y optimiza la orden de detención; esto no es un punto a punto.
Karussell

8

Los algoritmos gráficos como el algoritmo de Dijkstra no funcionarán porque el gráfico es enorme.

Este argumento no necesariamente se cumple porque Dijkstra generalmente no mirará el gráfico completo sino más bien un subconjunto muy pequeño (cuanto mejor interconectado esté el gráfico, más pequeño será este subconjunto).

Dijkstra en realidad puede funcionar bastante bien para gráficos bien comportados. Por otro lado, con una cuidadosa parametrización, A * siempre funcionará igual de bien o mejor. ¿Ya has probado cómo funcionaría con tus datos?

Dicho esto, también estaría muy interesado en escuchar sobre las experiencias de otras personas. Por supuesto, ejemplos destacados como la búsqueda de Google Map son particularmente interesantes. Me imagino algo así como un heurístico dirigido al vecino más cercano.


2
Suponga que está tratando de encontrar direcciones desde el punto A hasta el B, cuya distancia óptima es d. El algoritmo de Dijkstra examinará, como mínimo, todos los puntos a distancia como máximo d de A. Si A es San Francisco y B es Boston, esto significa que examina la mayor parte de los EE. UU. N'est-ce pas?
A. Rex

2
Sí lo es. ¡Lo que quería obtener es que se puede usar A * en su lugar y que siempre encuentra una solución óptima (a pesar de que utiliza una heurística)!
Konrad Rudolph el

Sí, por supuesto. Después de escribir mi publicación original, pensé en la palabra "heurística" que usé. Aquí es un poco impreciso porque encuentra una solución óptima.
A. Rex

2
Bueno, el punto es que A * usa una heurística (en lugar de ser uno) para determinar el siguiente paso. Esta decisión realmente puede ser subóptima. Pero solo afecta el tiempo de ejecución, no el resultado, ya que solo determina cuánto mejor que Dijstra adivina.
Konrad Rudolph el

8

El estado actual de la técnica en términos de tiempos de consulta para redes de carreteras estáticas es el algoritmo de etiquetado Hub propuesto por Abraham et al. http://link.springer.com/chapter/10.1007/978-3-642-20662-7_20 . Recientemente se publicó una encuesta completa y excelentemente escrita del campo como un informe técnico de Microsoft http://research.microsoft.com/pubs/207102/MSR-TR-2014-4.pdf .

La versión corta es ...

El algoritmo de etiquetado de Hub proporciona las consultas más rápidas para redes de carreteras estáticas, pero requiere una gran cantidad de ram para ejecutarse (18 GiB).

El enrutamiento del nodo de tránsito es un poco más lento, aunque solo requiere alrededor de 2 GiB de memoria y tiene un tiempo de preprocesamiento más rápido.

Las jerarquías de contracción proporcionan un buen equilibrio entre tiempos de preprocesamiento rápidos, requisitos de poco espacio (0.4 GiB) y tiempos de consulta rápidos.

Ningún algoritmo es completamente dominante ...

Esta charla sobre tecnología de Google por Peter Sanders puede ser de interés

https://www.youtube.com/watch?v=-0ErpE8tQbw

También esta charla de Andrew Goldberg

https://www.youtube.com/watch?v=WPrkc78XLhw

Una implementación de código abierto de las jerarquías de contracción está disponible en el sitio web del grupo de investigación Peter Sanders en KIT. http://algo2.iti.kit.edu/english/routeplanning.php

También una publicación de blog de fácil acceso escrita por Microsoft sobre el uso del algoritmo CRP ... http://blogs.bing.com/maps/2012/01/05/bing-maps-new-routing-engine/


7

Estoy un poco sorprendido de no ver el algoritmo de Floyd Warshall mencionado aquí. Este algoritmo funciona muy parecido al de Dijkstra. También tiene una característica muy buena que le permite calcular siempre que desee continuar permitiendo más vértices intermedios. Por lo tanto, naturalmente encontrará las rutas que usan autopistas interestatales o autopistas con bastante rapidez.


6

He hecho esto muchas veces, en realidad, probando varios métodos diferentes. Dependiendo del tamaño (geográfico) del mapa, es posible que desee considerar usar la función haversine como heurística.

La mejor solución que hice fue usar A * con una distancia en línea recta como función heurística. Pero luego necesita algún tipo de coordenadas para cada punto (intersección o vértice) en el mapa. También puede probar diferentes ponderaciones para la función heurística, es decir

f(n) = k*h(n) + g(n)

donde k es alguna constante mayor que 0.


4

Probablemente similar a la respuesta en rutas precalculadas entre ubicaciones principales y mapas en capas, pero entiendo que en los juegos, para acelerar A *, tienes un mapa que es muy grueso para la navegación macro, y un mapa de grano fino para navegación hasta el límite de las macro direcciones. Por lo tanto, tiene 2 rutas pequeñas para calcular y, por lo tanto, su espacio de búsqueda es mucho más pequeño que simplemente hacer una ruta única al destino. Y si está en el negocio de hacer esto mucho, tendría muchos de esos datos precalculados, por lo que al menos parte de la búsqueda es una búsqueda de datos precalculados, en lugar de una ruta.


3

Esto es pura especulación de mi parte, pero supongo que pueden usar una estructura de datos del mapa de influencia que se superpone al mapa dirigido para delimitar el dominio de búsqueda. Esto permitiría que el algoritmo de búsqueda dirija la ruta a las rutas principales cuando el viaje deseado sea largo.

Dado que esta es una aplicación de Google, también es razonable suponer que gran parte de la magia se realiza a través de un almacenamiento en caché extenso. :) No me sorprendería si el almacenamiento en caché de las solicitudes de ruta de Google Map más comunes del 5% permite una gran parte (20%? 50%?) De solicitudes que se responden mediante una simple búsqueda.


¿Tiene una buena referencia para "una estructura de datos del mapa de influencia"? ¡Gracias!
A. Rex

3

Tenía algunas ideas más sobre esto:

1) Recuerde que los mapas representan una organización física. Almacene la latitud / longitud de cada intersección. No necesita verificar mucho más allá de los puntos que se encuentran en la dirección de su objetivo. Solo si te encuentras bloqueado necesitas ir más allá de esto. Si almacena una superposición de conexiones superiores, puede limitarla aún más; normalmente, nunca cruzará una de ellas de una manera que se aleje de su destino final.

2) Divida el mundo en un conjunto de zonas definidas por conectividad limitada, defina todos los puntos de conectividad entre las zonas. Encuentre en qué zonas se encuentran su fuente y destino, para la ruta de la zona inicial y final desde su ubicación a cada punto de conexión, para las zonas entre simplemente mapa entre puntos de conexión. (Sospecho que mucho de esto último ya está precalculado).

Tenga en cuenta que las zonas pueden ser más pequeñas que un área metropolitana. Cualquier ciudad con características del terreno que la dividan (por ejemplo, un río) serían múltiples zonas.


3

Tenía mucha curiosidad sobre la heurística utilizada, cuando hace un tiempo obtuvimos rutas desde el mismo lugar de partida cerca de Santa Rosa, hasta dos campamentos diferentes en el Parque Nacional Yosemite. Estos diferentes destinos produjeron rutas bastante diferentes (a través de I-580 o CA-12) a pesar del hecho de que ambas rutas convergieron durante las últimas 100 millas (a lo largo de CA-120) antes de divergir nuevamente unas pocas millas al final. Esto fue bastante repetible. Las dos rutas estaban separadas por hasta 50 millas por alrededor de 100 millas, pero las distancias / tiempos eran bastante cercanas entre sí, como era de esperar.

Por desgracia, no puedo reproducir eso, los algoritmos deben haber cambiado. Pero me dio curiosidad sobre el algoritmo. Todo lo que puedo especular es que hubo una poda direccional que resultó ser exquisitamente sensible a la pequeña diferencia angular entre los destinos como se ve desde lejos, o hubo diferentes segmentos precalculados seleccionados por la elección del destino final.


3

Hablando de GraphHopper , un rápido planificador de rutas de código abierto basado en OpenStreetMap, he leído un poco de literatura e implementado algunos métodos. La solución más simple es un Dijkstra y una mejora simple es un Dijkstra bidireccional que explora aproximadamente solo la mitad de los nodos. Con Dijkstra bidireccional, una ruta a través de toda Alemania ya lleva 1 segundo (para el modo automóvil), en C probablemente sería solo 0.5 segundos más o menos;)

He creado un gif animado de una búsqueda de ruta real con Dijkstra bidireccional aquí . También hay algunas ideas más para hacer que Dijkstra sea más rápido, como hacer A *, que es un "Dijkstra orientado a objetivos". También he creado una animación gif para ello.

Pero, ¿cómo hacerlo (mucho) más rápido?

El problema es que para una búsqueda de ruta deben explorarse todos los nodos entre las ubicaciones y esto es realmente costoso, ya que en Alemania hay varios millones de ellos. Pero un punto de dolor adicional de Dijkstra, etc. es que tales búsquedas usan mucha RAM.

Existen soluciones heurísticas, pero también soluciones exactas que organizan el gráfico (red de carreteras) en capas jerárquicas, ambas tienen ventajas y desventajas y resuelven principalmente el problema de la velocidad y la RAM. He enumerado algunos de ellos en esta respuesta .

Para GraphHopper, decidí usar las Jerarquías de Contracción porque es relativamente 'fácil' de implementar y no toma años para preparar el gráfico. Todavía da como resultado tiempos de respuesta muy rápidos, como puede probar en nuestra instancia en línea GraphHopper Maps . Por ejemplo, desde Sudáfrica hasta el este de China, lo que da como resultado una distancia de 23000 km y casi 14 días de tiempo de conducción en automóvil y solo tomó ~ 0.1 segundos en el servidor.


Dijkstra bidireccional que utiliza puntos de referencia para realizar búsquedas dirigidas a objetivos es más eficiente que Dijkstra bidireccional solo. Ver www14.informatik.tu-muenchen.de/lehre/2010SS/sarntal/… Sin embargo, este documento no es lo suficientemente detallado como para calcular la función potencial, que es un poco difícil, o elegir los puntos de referencia.
Paul Chernoch

2

He trabajado en el enrutamiento durante algunos años, con una reciente explosión de actividad impulsada por las necesidades de mis clientes, y he descubierto que A * es fácilmente lo suficientemente rápido; Realmente no hay necesidad de buscar optimizaciones o algoritmos más complejos. Enrutar sobre un gráfico enorme no es un problema.

Pero la velocidad depende de tener toda la red de enrutamiento, con lo cual me refiero al gráfico dirigido de arcos y nodos que representan segmentos de ruta y uniones, respectivamente, en la memoria. La sobrecarga de tiempo principal es el tiempo necesario para crear esta red. Algunas cifras aproximadas basadas en una computadora portátil común con Windows y enrutamiento en toda España: tiempo necesario para crear la red: 10-15 segundos; Tiempo necesario para calcular una ruta: demasiado corto para medir.

La otra cosa importante es poder reutilizar la red para tantos cálculos de enrutamiento como desee. Si su algoritmo ha marcado los nodos de alguna manera para registrar la mejor ruta (costo total para el nodo actual y el mejor arco hacia él), como debe ser en A *, debe restablecer o borrar esta información anterior. En lugar de pasar por cientos de miles de nodos, es más fácil usar un sistema de generación de números. Marque cada nodo con el número de generación de sus datos; incremente el número de generación cuando calcule una nueva ruta; cualquier nodo con un número de generación anterior es obsoleto y su información puede ignorarse.


1

Veo qué pasa con los mapas en el OP:

Mire la ruta con el punto intermedio especificado: la ruta va un poco hacia atrás debido a que la carretera no es recta.

Si su algoritmo no retrocede, no verá la ruta más corta.


Idea interesante. He agregado otra violación en mi PPS al OP. Eche un vistazo y vea si puede ver una explicación allí.
A. Rex

Zoom CAMINO hacia abajo en el punto A - un clic desde máx. Tenga en cuenta cómo la ruta de tres puntos va al oeste, sur, este. Creo que estamos viendo un algoritmo al que no le gusta dar marcha atrás a menos que fuera necesario pasar por un punto de estrangulamiento.
Loren Pechtel

En mi ejemplo de PPS, cambie la dirección de inicio a "10 Avenue de Flandre, 75019 Paris". Esto elimina el pequeño retroceso del que estás hablando, pero el problema sigue ahí. Creo que el problema principal es que realmente quiere quedarse en ese Blvd principal ...
A. Rex

1
Creo que lo encontré en este caso: haga eso en automóvil y los tiempos tienen sentido. Probablemente ve el camino grande como más rápido y la ruta a pie no lo acelera.
Loren Pechtel

1
PD: El problema inicial también tiene sentido según este estándar, puede que no sea la pista que lo causó.
Loren Pechtel

0

Un algoritmo de ruta más corta de todos los pares calculará las rutas más cortas entre todos los vértices en un gráfico. Esto permitirá que las rutas se calculen previamente en lugar de requerir que se calcule una ruta cada vez que alguien quiera encontrar la ruta más corta entre una fuente y un destino. El algoritmo Floyd-Warshall es un algoritmo de ruta más corta de todos los pares.


-4

Los mapas nunca tienen en cuenta todo el mapa. Mi suposición es: 1. Según su ubicación, cargan un lugar y los puntos de referencia en ese lugar. 2. Cuando busca el destino, es cuando cargan la otra parte del mapa y hacen un gráfico de dos lugares y luego aplican los algoritmos de ruta más corta.

Además, existe una técnica importante de programación dinámica que sospecho se utiliza en el cálculo de las rutas más cortas. Puede referirse a eso también.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.