├── .dockerignore ├── .flake8 ├── .gitignore ├── .pylintrc ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── config ├── georef-ar-api.nginx ├── georef-ar-api.service ├── georef.example.cfg └── logging.example.ini ├── deploy ├── deploy_rsa.enc ├── travis.sh └── travis_georef.ovpn.enc ├── docker ├── .env ├── Dockerfile_api ├── docker-compose.yml └── georef.example.cfg ├── docs ├── .gitignore ├── 404.html ├── README.md ├── addresses │ └── index.html ├── applications │ └── index.html ├── assets │ ├── APIs y TS - Arquitectura.png │ ├── APIs y TS - Procesamiento de Request.png │ ├── arquitectura-time-series-ar-api.png │ ├── busqueda_excel.png │ ├── busqueda_generador.png │ ├── calles1.png │ ├── data-example-1.csv │ ├── departamentos1.png │ ├── django-rq-finished-jobs.png │ ├── django-rq.png │ ├── ejemplo_consulta.png │ ├── excel_letra.gif │ ├── excel_letra_1.png │ ├── excel_letra_10.png │ ├── excel_letra_11.png │ ├── excel_letra_12.png │ ├── excel_letra_13.png │ ├── excel_letra_14.png │ ├── excel_letra_2.png │ ├── excel_letra_3.png │ ├── excel_letra_4.png │ ├── excel_letra_5.png │ ├── excel_letra_6.png │ ├── excel_letra_7.png │ ├── excel_letra_8.png │ ├── excel_letra_9.png │ ├── fonts │ │ ├── font-awesome.css │ │ ├── material-icons.css │ │ └── specimen │ │ │ ├── FontAwesome.ttf │ │ │ ├── FontAwesome.woff │ │ │ ├── FontAwesome.woff2 │ │ │ ├── MaterialIcons-Regular.ttf │ │ │ ├── MaterialIcons-Regular.woff │ │ │ └── MaterialIcons-Regular.woff2 │ ├── generacion_consulta_generador.png │ ├── google_drive_1.png │ ├── google_drive_2.png │ ├── google_drive_3.png │ ├── google_drive_4.png │ ├── google_drive_5.png │ ├── google_drive_6.png │ ├── google_drive_7.png │ ├── google_drive_8.png │ ├── google_drive_9.png │ ├── google_drive_letra.gif │ ├── google_drive_letra_1.png │ ├── google_drive_letra_10.png │ ├── google_drive_letra_11.png │ ├── google_drive_letra_2.png │ ├── google_drive_letra_3.png │ ├── google_drive_letra_4.png │ ├── google_drive_letra_5.png │ ├── google_drive_letra_6.png │ ├── google_drive_letra_7.png │ ├── google_drive_letra_8.png │ ├── google_drive_letra_9.png │ ├── images │ │ ├── favicon.png │ │ └── icons │ │ │ ├── bitbucket.1b09e088.svg │ │ │ ├── github.f0b8504a.svg │ │ │ └── gitlab.6dd19c00.svg │ ├── javascripts │ │ ├── application.583bbe55.js │ │ ├── application.5e60981f.js │ │ ├── application.dc02f8ce.js │ │ ├── lunr │ │ │ ├── lunr.da.js │ │ │ ├── lunr.de.js │ │ │ ├── lunr.du.js │ │ │ ├── lunr.es.js │ │ │ ├── lunr.fi.js │ │ │ ├── lunr.fr.js │ │ │ ├── lunr.hu.js │ │ │ ├── lunr.it.js │ │ │ ├── lunr.ja.js │ │ │ ├── lunr.jp.js │ │ │ ├── lunr.multi.js │ │ │ ├── lunr.nl.js │ │ │ ├── lunr.no.js │ │ │ ├── lunr.pt.js │ │ │ ├── lunr.ro.js │ │ │ ├── lunr.ru.js │ │ │ ├── lunr.stemmer.support.js │ │ │ ├── lunr.sv.js │ │ │ ├── lunr.th.js │ │ │ ├── lunr.tr.js │ │ │ ├── tinyseg.js │ │ │ └── wordcut.js │ │ ├── modernizr.01ccdecf.js │ │ └── modernizr.1aa3b519.js │ ├── repeatable_jobs.png │ ├── scheduler.png │ └── stylesheets │ │ ├── application-palette.224b79ff.css │ │ ├── application-palette.22915126.css │ │ ├── application.3020aac5.css │ │ └── application.451f80e5.css ├── bulk │ └── index.html ├── communes │ └── index.html ├── css │ ├── extra.css │ └── pdf.css ├── deploy │ └── index.html ├── download │ └── index.html ├── etl-data │ └── index.html ├── etl-install │ └── index.html ├── etl │ └── index.html ├── fix_github_links.sh ├── geom-operations │ └── index.html ├── georef-api-data │ └── index.html ├── georef-api-development │ └── index.html ├── georef-ar-api-docs.html ├── georef-ar-api-docs.pdf ├── history │ └── index.html ├── images │ └── logo.svg ├── index.html ├── js │ └── extra.js ├── jwt-token │ └── index.html ├── localities │ └── index.html ├── municipalities │ └── index.html ├── open-api │ ├── .gitignore │ ├── README.md │ ├── absolute-path.js │ ├── georef-docs.js │ ├── gulpfile.js │ ├── index.html │ ├── index.js │ ├── oauth2-redirect.html │ ├── package.json │ ├── spec │ │ └── openapi.json │ ├── src │ │ ├── georef-docs.js │ │ └── index.html │ ├── swagger-ui-bundle.js │ ├── swagger-ui-standalone-preset.js │ ├── swagger-ui.css │ ├── swagger-ui.js │ ├── theme-feeling-blue.css │ ├── theme-flattop.css │ ├── theme-material.css │ ├── theme-monokai.css │ ├── theme-muted.css │ ├── theme-newspaper.css │ └── theme-outline.css ├── pdf.css ├── python-usage │ └── index.html ├── python3.6 │ └── index.html ├── quick-start │ └── index.html ├── search │ └── search_index.json ├── shapefiles │ └── index.html ├── sitemap.xml ├── sitemap.xml.gz ├── spreadsheet-integration │ └── index.html ├── src │ ├── addresses.md │ ├── applications.md │ ├── assets │ │ ├── calles1.png │ │ ├── departamentos1.png │ │ ├── google_drive_1.png │ │ ├── google_drive_2.png │ │ ├── google_drive_3.png │ │ ├── google_drive_4.png │ │ ├── google_drive_5.png │ │ ├── google_drive_6.png │ │ ├── google_drive_7.png │ │ ├── google_drive_8.png │ │ └── google_drive_9.png │ ├── bulk.md │ ├── communes.md │ ├── css │ │ └── extra.css │ ├── deploy.md │ ├── download.md │ ├── etl-data.md │ ├── etl-install.md │ ├── etl.md │ ├── geom-operations.md │ ├── georef-api-development.md │ ├── history.md │ ├── index.md │ ├── jwt-token.md │ ├── localities.md │ ├── python-usage.md │ ├── python3.6.md │ ├── quick-start.md │ ├── shapefiles.md │ ├── spreadsheet-integration.md │ └── terms.md └── terms │ └── index.html ├── mkdocs.yml ├── requirements-dev.txt ├── requirements-docs.txt ├── requirements.txt ├── service ├── __init__.py ├── address.py ├── constants.py ├── data.py ├── formatter.py ├── geometry.py ├── location.py ├── management │ ├── __init__.py │ ├── es_config.py │ ├── gunicorn_profile.py │ └── indexer.py ├── names.py ├── normalizer.py ├── params.py ├── query_result.py ├── routes.py ├── street.py ├── strings.py └── utils.py └── tests ├── README.md ├── __init__.py ├── test_mock_data.py ├── test_mock_formatter.py ├── test_mock_lfu_dict.py ├── test_mock_normalizer.py ├── test_mock_params.py ├── test_mock_point.py ├── test_mock_response.py ├── test_mock_routes.py ├── test_search_addresses_btwn.py ├── test_search_addresses_bulk.py ├── test_search_addresses_isct.py ├── test_search_addresses_simple.py ├── test_search_census_localities.py ├── test_search_departments.py ├── test_search_intersection.py ├── test_search_localities.py ├── test_search_location.py ├── test_search_municipalities.py ├── test_search_settlements.py ├── test_search_states.py └── test_search_streets.py /.dockerignore: -------------------------------------------------------------------------------- 1 | config 2 | source -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = N814, N812 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # Django stuff: 28 | *.log 29 | local_settings.py 30 | 31 | # Flask stuff: 32 | instance/ 33 | .webassets-cache 34 | 35 | # Scrapy stuff: 36 | .scrapy 37 | 38 | # Sphinx documentation 39 | docs/_build/ 40 | 41 | # PyBuilder 42 | target/ 43 | 44 | # IPython Notebook 45 | .ipynb_checkpoints 46 | 47 | # pyenv 48 | .python-version 49 | 50 | # dotenv 51 | .env 52 | 53 | # virtualenv 54 | venv/ 55 | ENV/ 56 | 57 | .DS_Store 58 | db.sqlite3 59 | .idea/ 60 | .vscode/ 61 | 62 | # Georef 63 | /backups/ 64 | environment.sh 65 | georef.cfg 66 | logging.ini 67 | /site/ 68 | /profile/ 69 | /config/georef_synonyms.txt 70 | .coverage 71 | htmlcov/ 72 | logs/ 73 | cache/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: required 3 | python: 4 | - '3.6' 5 | install: 6 | - pip install -r requirements.txt -r requirements-dev.txt 7 | - sudo apt-get update -qq 8 | - sudo apt-get install -y openvpn 9 | script: 10 | - make code_checks 11 | - make test_mock 12 | deploy: 13 | - provider: script 14 | skip_cleanup: true 15 | script: 'bash deploy/travis.sh production' 16 | on: 17 | tags: true 18 | branch: master 19 | repo: datosgobar/georef-ar-api 20 | - provider: script 21 | skip_cleanup: true 22 | script: 'bash deploy/travis.sh staging' 23 | on: 24 | branch: master 25 | repo: datosgobar/georef-ar-api 26 | - provider: script 27 | skip_cleanup: true 28 | script: 'bash deploy/travis.sh development' 29 | on: 30 | branch: development 31 | repo: datosgobar/georef-ar-api 32 | before_deploy: 33 | - openssl aes-256-cbc -K $encrypted_4c0bf4b7e010_key -iv $encrypted_4c0bf4b7e010_iv 34 | -in deploy/deploy_rsa.enc -out /tmp/deploy_rsa -d 35 | - eval "$(ssh-agent -s)" 36 | - chmod 600 /tmp/deploy_rsa 37 | - ssh-add /tmp/deploy_rsa 38 | - openssl aes-256-cbc -K $encrypted_5f43eadfe7ce_key -iv $encrypted_5f43eadfe7ce_iv 39 | -in deploy/travis_georef.ovpn.enc -out travis_georef.ovpn -d 40 | - sudo openvpn --config travis_georef.ovpn --daemon --verb 3 --writepid openvpn.pid --log openvpn.log 41 | - sleep 10 42 | - sudo ip link set tun0 mtu 1200 43 | after_deploy: 44 | - sudo kill -SIGTERM $(cat openvpn.pid) 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Dirección Nacional de Datos e Información Pública 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile para georef-ar-api 2 | # 3 | # Contiene recetas para ejecutar tests, correr servidores de prueba 4 | # y generar la documentación. 5 | 6 | CFG_PATH ?= config/georef.cfg 7 | EXAMPLE_CFG_PATH = config/georef.example.cfg 8 | INDEX_NAME ?= all 9 | INDEXER_PY = service.management.indexer 10 | 11 | .PHONY: docs 12 | 13 | check_config_file: 14 | @test -f $(CFG_PATH) || \ 15 | (echo "No existe el archivo de configuración $(CFG_PATH)." && exit 1) 16 | 17 | index: check_config_file 18 | GEOREF_CONFIG=$(CFG_PATH) \ 19 | python -m $(INDEXER_PY) -m index -n $(INDEX_NAME) -v 20 | 21 | index_forced: check_config_file 22 | GEOREF_CONFIG=$(CFG_PATH) \ 23 | python -m $(INDEXER_PY) -m index -n $(INDEX_NAME) -f -v 24 | 25 | print_index_stats: check_config_file 26 | GEOREF_CONFIG=$(CFG_PATH) \ 27 | python -m $(INDEXER_PY) -m index_stats -i 28 | 29 | start_dev_server: check_config_file 30 | GEOREF_CONFIG=$(CFG_PATH) \ 31 | FLASK_APP=service/__init__.py \ 32 | FLASK_ENV=development \ 33 | flask run 34 | 35 | start_gunicorn_dev_server: check_config_file 36 | GEOREF_CONFIG=$(CFG_PATH) \ 37 | gunicorn service:app -w 4 -k gevent --log-config=config/logging.ini -b 127.0.0.1:5000 38 | 39 | start_profile_server: 40 | GEOREF_CONFIG=$(EXAMPLE_CFG_PATH) \ 41 | gunicorn service:app -c service/management/gunicorn_profile.py -b 127.0.0.1:5000 42 | 43 | test_live: 44 | GEOREF_CONFIG=$(EXAMPLE_CFG_PATH) \ 45 | python -m unittest discover -p test_search_* 46 | 47 | test_mock: 48 | GEOREF_CONFIG=$(EXAMPLE_CFG_PATH) \ 49 | python -m unittest discover -p test_mock_* 50 | 51 | # TEST_FILES se puede definir opcionalmente 52 | test: 53 | GEOREF_CONFIG=$(EXAMPLE_CFG_PATH) \ 54 | python -m unittest $(TEST_FILES) 55 | 56 | code_checks: 57 | flake8 tests/ service/ 58 | pylint tests/ service/ 59 | 60 | coverage: 61 | GEOREF_CONFIG=$(EXAMPLE_CFG_PATH) \ 62 | coverage run --source=service --omit=service/management/* -m unittest 63 | coverage report 64 | 65 | console: 66 | GEOREF_CONFIG=$(CFG_PATH) \ 67 | python -c 'import service; service.georef_console()' 68 | 69 | docs: 70 | mkdocs build 71 | rsync -vau --remove-source-files docs/site/ docs/ 72 | rm -rf docs/site 73 | 74 | servedocs: 75 | mkdocs serve 76 | 77 | pdf: 78 | mkdocs_datosgobar md2pdf mkdocs.yml docs/georef-ar-api-docs.pdf 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # georef-ar-api 2 | [![Build Status](https://travis-ci.org/datosgobar/georef-ar-api.svg?branch=master)](https://travis-ci.org/datosgobar/georef-ar-api) 3 | ![Docs Status](https://readthedocs.org/projects/georef-ar-api/badge/?version=latest) 4 | ![](https://img.shields.io/github/license/datosgobar/georef-ar-api.svg) 5 | ![](https://img.shields.io/badge/python-3-blue.svg) 6 | 7 | API del Servicio de Normalización de Datos Geográficos de Argentina. 8 | 9 | ## Documentación 10 | Para consultar la documentación de la API, acceder a [https://apis.datos.gob.ar/georef](https://apis.datos.gob.ar/georef). 11 | 12 | ## Contenedores 13 | Para correr los contenedores asegúrate de tener instalado docker-compose\ 14 | El archivo de configuración puede correr dos servicios creando los siguientes contenedores: 15 | - georef-api_es01: Un contenedor con Elasticsearch procesar e indexar los datos. Estos datos son almacenados y persistidos en un volumen de docker. 16 | - georef-api_app: Un contenedor con la aplicación. Al correrlo la primera vez es necesario correr una indexación. 17 | 18 | Antes de levantar el servicio de la app deberás generar un archivo config/georef.cfg que puedes hacerlo copiando el que ya existe (docker/georef.example.cfg) y renombrando ciertas variables. 19 | Tener en cuenta que dentro de la red de docker por defecto el host para el servidor de Elasticsearch es "es01" y el puerto es 9200.\ 20 | Las carpetas config/; source/; backups/ y logs/ serán montadas dentro del contenedor de georef-api_app y se podrá acceder desde el host a los archivos generados por la app. 21 | 22 | El archivo de configuración docker/georef.example.cfg está preparado para ser copiado al destino config/georef.cfg\ 23 | La fuente de datos está configurada para ser leida desde la carpeta source/; pero si se desea se puede cambiar el archivo georef.cfg para especificar otra ruta o una URL. 24 | Si bien se puede copiar los archivos fuente dentro de /source, se recomienda crear un enlace duro; sobre todo si se están haciendo pruebas con el ETL en el mismo entorno de desarrollo. 25 | 26 | ``` 27 | cp -rl /home/georef-etl/files/latest /home/georef-api/source 28 | ``` 29 | Si se encuentra en otra partición se puede optar por un enlace simbólico: 30 | 31 | ``` 32 | ln /home/georef-etl/files/latest /home/georef-api/source 33 | ``` 34 | 35 | Para correr la aplicación: 36 | 37 | Situarse dentro de la carpeta "docker" y correr el siguiente comando: 38 | 39 | ``` 40 | docker-compose up -d 41 | ``` 42 | 43 | Para indexar los archivos generados por el ETL correr el siguiente comando: 44 | 45 | ``` 46 | docker-compose exec app make index 47 | ``` 48 | 49 | Nota: Para más comandos referirse a la documentación (https://datosgobar.github.io/georef-ar-api/georef-api-development/#3-crear-los-indices) 50 | 51 | El puerto utilizado por la aplicación es el 5000 y se mapea al mismo puerto del host. Ambos valores pueden ser modificados en el archivo docker-compose.yml 52 | 53 | Para realizar consultas a la api se puede hacer una petición desde el host a dicho puerto. 54 | 55 | Ejemplo: 56 | 57 | ``` 58 | curl localhost:5000/api/provincias 59 | ``` 60 | 61 | Nota: Para más endpoints referirse a la documentación (https://datosgobar.github.io/georef-ar-api/open-api/) 62 | 63 | Si se modifica el código fuente reconstruir la imagen 64 | 65 | `docker-compose build app` 66 | 67 | ## Soporte 68 | En caso de que encuentres algún bug, tengas problemas con la instalación, o tengas comentarios de alguna parte de Georef API, podés mandarnos un mail a [datosargentina@jefatura.gob.ar](mailto:datosargentina@jefatura.gob.ar) o [crear un issue](https://github.com/datosgobar/georef-ar-api/issues/new?title=Encontre-un-bug-en-georef-ar-api). 69 | -------------------------------------------------------------------------------- /config/georef-ar-api.nginx: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | listen [::]:80 default_server; 4 | 5 | # Descomentar las siguientes dos líneas para activar el uso de 6 | # cache de nginx. El archivo de configuración nginx.conf DEBE 7 | # contener la directiva 'proxy_cache_path' activada con keys_zone=georef 8 | 9 | # proxy_cache georef; 10 | # proxy_cache_valid 200 120m; 11 | 12 | location / { 13 | proxy_pass http://localhost:5000; 14 | } 15 | } -------------------------------------------------------------------------------- /config/georef-ar-api.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=georef-ar-api gunicorn daemon 3 | After=network.target 4 | 5 | [Service] 6 | PIDFile=/run/gunicorn/api-pid 7 | Group=www-data 8 | RuntimeDirectory=gunicorn 9 | 10 | # Completar el usuario 11 | User= 12 | 13 | # Completar la ruta a georef-ar-api 14 | WorkingDirectory= 15 | 16 | # Completar la ruta a georef-ar-api 17 | ExecStart=/env/bin/gunicorn \ 18 | --pid /run/gunicorn/api-pid \ 19 | -w 4 -k gevent \ 20 | --bind 0.0.0.0:5000 service:app \ 21 | --log-config config/logging.ini 22 | 23 | ExecReload=/bin/kill -s HUP $MAINPID 24 | ExecStop=/bin/kill -s TERM $MAINPID 25 | PrivateTmp=true 26 | Environment="GEOREF_CONFIG=config/georef.cfg" 27 | 28 | [Install] 29 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /config/logging.example.ini: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root, elasticsearch, flask, werkzeug, gunicorn, georef 3 | 4 | [handlers] 5 | keys=console 6 | 7 | [formatters] 8 | keys=general 9 | 10 | [logger_root] 11 | level=WARNING 12 | handlers=console 13 | 14 | [logger_elasticsearch] 15 | level=WARNING 16 | handlers=console 17 | qualname=elasticsearch 18 | 19 | [logger_flask] 20 | level=INFO 21 | handlers=console 22 | qualname=flask 23 | 24 | [logger_werkzeug] 25 | level=INFO 26 | handlers=console 27 | qualname=werkzeug 28 | 29 | [logger_gunicorn] 30 | level=INFO 31 | handlers=console 32 | qualname=gunicorn 33 | propagate=0 34 | 35 | [logger_georef] 36 | level=WARNING 37 | handlers=console 38 | qualname=georef 39 | propagate=0 40 | 41 | [handler_console] 42 | class=StreamHandler 43 | formatter=general 44 | level=INFO 45 | args=(sys.stdout, ) 46 | 47 | [formatter_general] 48 | format=%(name)s - %(levelname)s: %(message)s -------------------------------------------------------------------------------- /deploy/deploy_rsa.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/deploy/deploy_rsa.enc -------------------------------------------------------------------------------- /deploy/travis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # script de deploy TravisCI para georef-ar-api 3 | 4 | readonly reindex_regex="\[force reindex\]" 5 | 6 | echo "- Iniciando el script de deploy para georef-ar-api..." 7 | 8 | # Lectura de variables dependiendo del entorno seleccionado 9 | 10 | case "$1" in 11 | production) 12 | echo '- Entorno: production' 13 | IP=$IP_PROD 14 | PORT_SSH=$PORT_SSH_PROD 15 | USER_SSH=$USER_SSH_PROD 16 | BRANCH=$BRANCH_PROD 17 | ;; 18 | 19 | staging) 20 | echo '- Entorno: staging' 21 | IP=$IP_STG 22 | PORT_SSH=$PORT_SSH_STG 23 | USER_SSH=$USER_SSH_STG 24 | BRANCH=$BRANCH_STG 25 | ;; 26 | 27 | development) 28 | echo '- Entorno: development' 29 | IP=$IP_DEV 30 | PORT_SSH=$PORT_SSH_DEV 31 | USER_SSH=$USER_SSH_DEV 32 | BRANCH=$BRANCH_DEV 33 | ;; 34 | *) 35 | echo 'No se especificó un entorno.' 36 | exit 1 37 | esac 38 | 39 | echo "- Agregando host a known hosts..." 40 | ssh-keyscan -p "$PORT_SSH" "$IP" >> ~/.ssh/known_hosts 41 | 42 | # Re-deploy de aplicación Flask y reinicio de servicio 43 | 44 | DEPLOY_SCRIPT=" 45 | cd $GEOREF_API_DIR; 46 | echo '- Activando virtualenv...' 47 | source venv/bin/activate 48 | echo '- Pulleando branch git...' 49 | git pull origin $BRANCH 50 | echo '- Actualizando dependencias...' 51 | pip install --upgrade --force-reinstall -r requirements.txt 52 | echo '- Reiniciando servicio...' 53 | sudo systemctl restart georef-api 54 | echo 'Listo.' 55 | " 56 | 57 | echo "- Haciendo ssh a la máquina y corriendo script de deploy..." 58 | ssh -p "$PORT_SSH" "$USER_SSH"@"$IP" "$DEPLOY_SCRIPT" 59 | 60 | # Re-indexación opcional de datos 61 | 62 | REINDEX_SCRIPT=" 63 | cd $GEOREF_API_DIR 64 | source venv/bin/activate 65 | make index_forced 66 | " 67 | 68 | if [[ "$TRAVIS_COMMIT_MESSAGE" =~ $reindex_regex ]]; then 69 | echo "- Activando script de re-indexación..." 70 | ssh -p "$PORT_SSH" "$USER_SSH"@"$IP" "$REINDEX_SCRIPT" 71 | else 72 | echo "- No se especificó una re-indexación." 73 | fi 74 | -------------------------------------------------------------------------------- /deploy/travis_georef.ovpn.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/deploy/travis_georef.ovpn.enc -------------------------------------------------------------------------------- /docker/.env: -------------------------------------------------------------------------------- 1 | COMPOSE_PROJECT_NAME=georef-api 2 | ELASTIC_STACK_VERSION=7.17.9 3 | JAVA_OPTS=-Xmx4096m -Xms4096m 4 | FLASK_ENV=development 5 | -------------------------------------------------------------------------------- /docker/Dockerfile_api: -------------------------------------------------------------------------------- 1 | FROM python:3.9 2 | 3 | RUN apt-get update && apt-get -y upgrade && apt-get install -y libgeos3.11.1 libgeos-c1v5 4 | 5 | RUN pip install --upgrade pip 6 | 7 | WORKDIR /opt/app-root/src/georef-ar-api 8 | 9 | COPY requirements.txt . 10 | RUN pip install -r requirements.txt 11 | 12 | COPY . . -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.2' 2 | services: 3 | # for three nodes see: https://www.elastic.co/guide/en/elasticsearch/reference/7.5/docker.html#docker-compose-file 4 | es01: 5 | image: docker.elastic.co/elasticsearch/elasticsearch:$ELASTIC_STACK_VERSION 6 | container_name: georef-api_es01 7 | environment: 8 | - node.name=es01 9 | - cluster.name=es-docker-cluster 10 | - discovery.type=single-node 11 | - bootstrap.memory_lock=true # disable swapping out parts of the JVM heap to disk (https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration-memory.html) 12 | - "ES_JAVA_OPTS=$JAVA_OPTS" 13 | ulimits: 14 | memlock: # configures the range of memory that ElasticSearch will use. Setting this to –1 means unlimited. 15 | soft: -1 16 | hard: -1 17 | volumes: 18 | - data01:/usr/share/elasticsearch/data 19 | ports: 20 | - 9200:9200 21 | networks: 22 | - geonet 23 | app: 24 | build: 25 | context: .. 26 | dockerfile: ./docker/Dockerfile_api 27 | image: datosgobar/georef-api_app 28 | container_name: georef-api_app 29 | environment: 30 | - "GEOREF_CONFIG=config/georef.cfg" 31 | - "FLASK_APP=service/__init__.py" 32 | - "FLASK_ENV=development" 33 | command: flask run --host=0.0.0.0 --port=5000 34 | volumes: 35 | - ../config:/opt/app-root/src/georef-ar-api/config:ro 36 | - ../source:/opt/app-root/src/georef-ar-api/source 37 | - ../logs:/opt/app-root/src/georef-ar-api/logs 38 | - ../backups:/opt/app-root/src/georef-ar-api/backups 39 | ports: 40 | - 8080:5000 41 | networks: 42 | - geonet 43 | 44 | volumes: 45 | data01: 46 | driver: local 47 | 48 | networks: 49 | geonet: 50 | driver: bridge 51 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | /site -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # georef-ar-pi - Documentación 2 | 3 | Este directorio contiene todos los archivos relacionados a la documentación de `georef-ar-api` y `georef-ar-etl`. 4 | 5 | ## Archivos 6 | 7 | El propósito de cada archivo/directorio incluido se detalla a continuación: 8 | - `open-api/`: Contiene el archivo de documentación OpenAPI, y archivos de la página Swagger UI generada a partir del mismo. Para más información, acceder al archivo [REAMDE.md](open-api/README.md) dentro del directorio. 9 | - `src/`: Contiene archivos Markdown que documentan el uso y desarrollo de `georef-ar-api` y `georef-ar-etl`. También contiene imágenes y archivos CSS necesarios para generar las páginas de documentación HTML utilizando *Read the Docs*. 10 | - `.gitignore`: Asegura que el directorio `site/` (donde se generan inicialmente las páginas *Read the Docs*) no se incluya en el repositorio remoto. 11 | - `*`: Cualquier otro archivo o directorio forma parte de las páginas de documentación generadas por *Read the Docs*. Estos contenidos están contenidos directamente bajo el directorio `docs/` para permitir su uso con GitHub Pages. **No modificar estos archivos y directorios manualmente.** 12 | 13 | ## Desarrollo de Documentación 14 | 15 | Luego de editar/crear cualquier archivo `.md` dentro de `src/`, se deben actualizar las páginas *Read the Docs*, y luego se deben cargar los archivos a GitHub Pages para que puedan ser accedidos a través de [https://datosgobar.github.io/georef-ar-api](https://datosgobar.github.io/georef-ar-api). **Se recomienda no modificar los nombres de archivos ya existentes, porque esto modificaría la URL pública de esa sección de documentación.** 16 | 17 | Para actualizar la documentación, entonces, seguir los siguientes pasos: 18 | 19 | 1. Desde la raíz del proyecto, activar un entorno virtual Python y luego instalar las dependencias necesarias: 20 | ```bash 21 | (venv) $ pip install -r requirements-docs.txt 22 | ``` 23 | 24 | 2. Generar las nuevas páginas con *Read the Docs*: 25 | ```bash 26 | (venv) $ make docs 27 | ``` 28 | 29 | 3. Revisar los cambios generados con `git`. **Si aparecen cambios que no corresponden a los archivos modificados/creados, es probable que se esté utilizando una versión de `mkdocs-datosgobar` incorrecta (o de otra dependencia).** 30 | 31 | 4. Subir los cambios a la rama `master`: 32 | ```bash 33 | (venv) $ git add docs/ 34 | (venv) $ git commit -m " [skip ci]" 35 | (venv) $ git push origin master 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/assets/APIs y TS - Arquitectura.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/APIs y TS - Arquitectura.png -------------------------------------------------------------------------------- /docs/assets/APIs y TS - Procesamiento de Request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/APIs y TS - Procesamiento de Request.png -------------------------------------------------------------------------------- /docs/assets/arquitectura-time-series-ar-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/arquitectura-time-series-ar-api.png -------------------------------------------------------------------------------- /docs/assets/busqueda_excel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/busqueda_excel.png -------------------------------------------------------------------------------- /docs/assets/busqueda_generador.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/busqueda_generador.png -------------------------------------------------------------------------------- /docs/assets/calles1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/calles1.png -------------------------------------------------------------------------------- /docs/assets/data-example-1.csv: -------------------------------------------------------------------------------- 1 | indice_tiempo,pesos_argentinos_por_dolar_estadounidense,ipc_2016_nucleo,ipc_2016_nivgeneral 2016-01-01,13.6099238,, 2016-02-01,14.7951857,, 2016-03-01,14.8990696,, 2016-04-01,14.3879762,, 2016-05-01,14.1183,0.0265777,0.0419337 2016-06-01,14.1542409,0.0301247,0.0307591 2016-07-01,14.8882952,0.0187154,0.0204675 2016-08-01,14.8339304,0.0165157,0.0020196 2016-09-01,15.1174273,0.0154869,0.0114914 2016-10-01,15.1745429,0.0179986,0.0235933 2016-11-01,15.3529636,0.0171879,0.0161842 2016-12-01,15.8815,0.0171243,0.0119757 2017-01-01,15.9102773,0.013389,0.01313 2017-02-01,15.580245,0.0184608,0.0246316 2017-03-01,15.5240391,0.0182095,0.0236416 2017-04-01,15.33552,0.0229158,0.0263366 2017-05-01,15.7173087,0.0160246,0.0128313 2017-06-01,16.1231909,0.0152262,0.0138837 2017-07-01,17.1859524,0.0176384,0.0171937 2017-08-01,17.414913,0.0150374,0.0147753 2017-09-01,17.2411857,0.017578,0.0204363 2017-10-01,17.4550318,, -------------------------------------------------------------------------------- /docs/assets/departamentos1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/departamentos1.png -------------------------------------------------------------------------------- /docs/assets/django-rq-finished-jobs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/django-rq-finished-jobs.png -------------------------------------------------------------------------------- /docs/assets/django-rq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/django-rq.png -------------------------------------------------------------------------------- /docs/assets/ejemplo_consulta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/ejemplo_consulta.png -------------------------------------------------------------------------------- /docs/assets/excel_letra.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/excel_letra.gif -------------------------------------------------------------------------------- /docs/assets/excel_letra_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/excel_letra_1.png -------------------------------------------------------------------------------- /docs/assets/excel_letra_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/excel_letra_10.png -------------------------------------------------------------------------------- /docs/assets/excel_letra_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/excel_letra_11.png -------------------------------------------------------------------------------- /docs/assets/excel_letra_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/excel_letra_12.png -------------------------------------------------------------------------------- /docs/assets/excel_letra_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/excel_letra_13.png -------------------------------------------------------------------------------- /docs/assets/excel_letra_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/excel_letra_14.png -------------------------------------------------------------------------------- /docs/assets/excel_letra_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/excel_letra_2.png -------------------------------------------------------------------------------- /docs/assets/excel_letra_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/excel_letra_3.png -------------------------------------------------------------------------------- /docs/assets/excel_letra_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/excel_letra_4.png -------------------------------------------------------------------------------- /docs/assets/excel_letra_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/excel_letra_5.png -------------------------------------------------------------------------------- /docs/assets/excel_letra_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/excel_letra_6.png -------------------------------------------------------------------------------- /docs/assets/excel_letra_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/excel_letra_7.png -------------------------------------------------------------------------------- /docs/assets/excel_letra_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/excel_letra_8.png -------------------------------------------------------------------------------- /docs/assets/excel_letra_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/excel_letra_9.png -------------------------------------------------------------------------------- /docs/assets/fonts/material-icons.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 3 | * use this file except in compliance with the License. You may obtain a copy 4 | * of the License at: 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, SOFTWARE 9 | * DISTRIBUTED UNDER THE LICENSE IS DISTRIBUTED ON AN "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. 11 | * SEE THE LICENSE FOR THE SPECIFIC LANGUAGE GOVERNING PERMISSIONS AND 12 | * LIMITATIONS UNDER THE LICENSE. 13 | */@font-face{font-family:"Material Icons";font-style:normal;font-weight:400;src:local("Material Icons"),local("MaterialIcons-Regular"),url("specimen/MaterialIcons-Regular.woff2") format("woff2"),url("specimen/MaterialIcons-Regular.woff") format("woff"),url("specimen/MaterialIcons-Regular.ttf") format("truetype")} -------------------------------------------------------------------------------- /docs/assets/fonts/specimen/FontAwesome.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/fonts/specimen/FontAwesome.ttf -------------------------------------------------------------------------------- /docs/assets/fonts/specimen/FontAwesome.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/fonts/specimen/FontAwesome.woff -------------------------------------------------------------------------------- /docs/assets/fonts/specimen/FontAwesome.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/fonts/specimen/FontAwesome.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/specimen/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/fonts/specimen/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /docs/assets/fonts/specimen/MaterialIcons-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/fonts/specimen/MaterialIcons-Regular.woff -------------------------------------------------------------------------------- /docs/assets/fonts/specimen/MaterialIcons-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/fonts/specimen/MaterialIcons-Regular.woff2 -------------------------------------------------------------------------------- /docs/assets/generacion_consulta_generador.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/generacion_consulta_generador.png -------------------------------------------------------------------------------- /docs/assets/google_drive_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_1.png -------------------------------------------------------------------------------- /docs/assets/google_drive_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_2.png -------------------------------------------------------------------------------- /docs/assets/google_drive_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_3.png -------------------------------------------------------------------------------- /docs/assets/google_drive_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_4.png -------------------------------------------------------------------------------- /docs/assets/google_drive_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_5.png -------------------------------------------------------------------------------- /docs/assets/google_drive_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_6.png -------------------------------------------------------------------------------- /docs/assets/google_drive_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_7.png -------------------------------------------------------------------------------- /docs/assets/google_drive_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_8.png -------------------------------------------------------------------------------- /docs/assets/google_drive_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_9.png -------------------------------------------------------------------------------- /docs/assets/google_drive_letra.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_letra.gif -------------------------------------------------------------------------------- /docs/assets/google_drive_letra_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_letra_1.png -------------------------------------------------------------------------------- /docs/assets/google_drive_letra_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_letra_10.png -------------------------------------------------------------------------------- /docs/assets/google_drive_letra_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_letra_11.png -------------------------------------------------------------------------------- /docs/assets/google_drive_letra_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_letra_2.png -------------------------------------------------------------------------------- /docs/assets/google_drive_letra_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_letra_3.png -------------------------------------------------------------------------------- /docs/assets/google_drive_letra_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_letra_4.png -------------------------------------------------------------------------------- /docs/assets/google_drive_letra_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_letra_5.png -------------------------------------------------------------------------------- /docs/assets/google_drive_letra_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_letra_6.png -------------------------------------------------------------------------------- /docs/assets/google_drive_letra_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_letra_7.png -------------------------------------------------------------------------------- /docs/assets/google_drive_letra_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_letra_8.png -------------------------------------------------------------------------------- /docs/assets/google_drive_letra_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/google_drive_letra_9.png -------------------------------------------------------------------------------- /docs/assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/assets/images/favicon.png -------------------------------------------------------------------------------- /docs/assets/images/icons/bitbucket.1b09e088.svg: -------------------------------------------------------------------------------- 1 | 3 | 20 | 21 | -------------------------------------------------------------------------------- /docs/assets/images/icons/github.f0b8504a.svg: -------------------------------------------------------------------------------- 1 | 3 | 18 | 19 | -------------------------------------------------------------------------------- /docs/assets/images/icons/gitlab.6dd19c00.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | 9 | 12 | 13 | 14 | 17 | 18 | 19 | 22 | 23 | 24 | 27 | 28 | 29 | 32 | 33 | 34 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /docs/assets/javascripts/lunr/lunr.da.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r,i,n;e.da=function(){this.pipeline.reset(),this.pipeline.add(e.da.trimmer,e.da.stopWordFilter,e.da.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.da.stemmer))},e.da.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.da.trimmer=e.trimmerSupport.generateTrimmer(e.da.wordCharacters),e.Pipeline.registerFunction(e.da.trimmer,"trimmer-da"),e.da.stemmer=(r=e.stemmerSupport.Among,i=e.stemmerSupport.SnowballProgram,n=new function(){var e,n,t,s=[new r("hed",-1,1),new r("ethed",0,1),new r("ered",-1,1),new r("e",-1,1),new r("erede",3,1),new r("ende",3,1),new r("erende",5,1),new r("ene",3,1),new r("erne",3,1),new r("ere",3,1),new r("en",-1,1),new r("heden",10,1),new r("eren",10,1),new r("er",-1,1),new r("heder",13,1),new r("erer",13,1),new r("s",-1,2),new r("heds",16,1),new r("es",16,1),new r("endes",18,1),new r("erendes",19,1),new r("enes",18,1),new r("ernes",18,1),new r("eres",18,1),new r("ens",16,1),new r("hedens",24,1),new r("erens",24,1),new r("ers",16,1),new r("ets",16,1),new r("erets",28,1),new r("et",-1,1),new r("eret",30,1)],o=[new r("gd",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1)],a=[new r("ig",-1,1),new r("lig",0,1),new r("elig",1,1),new r("els",-1,1),new r("løst",-1,2)],d=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],u=[239,254,42,3,0,0,0,0,0,0,0,0,0,0,0,0,16],c=new i;function l(){var e,r=c.limit-c.cursor;c.cursor>=n&&(e=c.limit_backward,c.limit_backward=n,c.ket=c.cursor,c.find_among_b(o,4)?(c.bra=c.cursor,c.limit_backward=e,c.cursor=c.limit-r,c.cursor>c.limit_backward&&(c.cursor--,c.bra=c.cursor,c.slice_del())):c.limit_backward=e)}this.setCurrent=function(e){c.setCurrent(e)},this.getCurrent=function(){return c.getCurrent()},this.stem=function(){var r,i=c.cursor;return function(){var r,i=c.cursor+3;if(n=c.limit,0<=i&&i<=c.limit){for(e=i;;){if(r=c.cursor,c.in_grouping(d,97,248)){c.cursor=r;break}if(c.cursor=r,r>=c.limit)return;c.cursor++}for(;!c.out_grouping(d,97,248);){if(c.cursor>=c.limit)return;c.cursor++}(n=c.cursor)=n&&(r=c.limit_backward,c.limit_backward=n,c.ket=c.cursor,e=c.find_among_b(s,32),c.limit_backward=r,e))switch(c.bra=c.cursor,e){case 1:c.slice_del();break;case 2:c.in_grouping_b(u,97,229)&&c.slice_del()}}(),c.cursor=c.limit,l(),c.cursor=c.limit,function(){var e,r,i,t=c.limit-c.cursor;if(c.ket=c.cursor,c.eq_s_b(2,"st")&&(c.bra=c.cursor,c.eq_s_b(2,"ig")&&c.slice_del()),c.cursor=c.limit-t,c.cursor>=n&&(r=c.limit_backward,c.limit_backward=n,c.ket=c.cursor,e=c.find_among_b(a,5),c.limit_backward=r,e))switch(c.bra=c.cursor,e){case 1:c.slice_del(),i=c.limit-c.cursor,l(),c.cursor=c.limit-i;break;case 2:c.slice_from("løs")}}(),c.cursor=c.limit,c.cursor>=n&&(r=c.limit_backward,c.limit_backward=n,c.ket=c.cursor,c.out_grouping_b(d,97,248)?(c.bra=c.cursor,t=c.slice_to(t),c.limit_backward=r,c.eq_v_b(t)&&c.slice_del()):c.limit_backward=r),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}),e.Pipeline.registerFunction(e.da.stemmer,"stemmer-da"),e.da.stopWordFilter=e.generateStopWordFilter("ad af alle alt anden at blev blive bliver da de dem den denne der deres det dette dig din disse dog du efter eller en end er et for fra ham han hans har havde have hende hendes her hos hun hvad hvis hvor i ikke ind jeg jer jo kunne man mange med meget men mig min mine mit mod ned noget nogle nu når og også om op os over på selv sig sin sine sit skal skulle som sådan thi til ud under var vi vil ville vor være været".split(" ")),e.Pipeline.registerFunction(e.da.stopWordFilter,"stopWordFilter-da")}}); -------------------------------------------------------------------------------- /docs/assets/javascripts/lunr/lunr.de.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r,n,i;e.de=function(){this.pipeline.reset(),this.pipeline.add(e.de.trimmer,e.de.stopWordFilter,e.de.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.de.stemmer))},e.de.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.de.trimmer=e.trimmerSupport.generateTrimmer(e.de.wordCharacters),e.Pipeline.registerFunction(e.de.trimmer,"trimmer-de"),e.de.stemmer=(r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){var e,i,s,t=[new r("",-1,6),new r("U",0,2),new r("Y",0,1),new r("ä",0,3),new r("ö",0,4),new r("ü",0,5)],o=[new r("e",-1,2),new r("em",-1,1),new r("en",-1,2),new r("ern",-1,1),new r("er",-1,1),new r("s",-1,3),new r("es",5,2)],c=[new r("en",-1,1),new r("er",-1,1),new r("st",-1,2),new r("est",2,1)],u=[new r("ig",-1,1),new r("lich",-1,1)],a=[new r("end",-1,1),new r("ig",-1,2),new r("ung",-1,1),new r("lich",-1,3),new r("isch",-1,2),new r("ik",-1,2),new r("heit",-1,3),new r("keit",-1,4)],d=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32,8],l=[117,30,5],m=[117,30,4],h=new n;function w(e,r,n){return!(!h.eq_s(1,e)||(h.ket=h.cursor,!h.in_grouping(d,97,252)))&&(h.slice_from(r),h.cursor=n,!0)}function f(){for(;!h.in_grouping(d,97,252);){if(h.cursor>=h.limit)return!0;h.cursor++}for(;!h.out_grouping(d,97,252);){if(h.cursor>=h.limit)return!0;h.cursor++}return!1}function b(){return s<=h.cursor}function _(){return i<=h.cursor}this.setCurrent=function(e){h.setCurrent(e)},this.getCurrent=function(){return h.getCurrent()},this.stem=function(){var r=h.cursor;return function(){for(var e,r,n,i,s=h.cursor;;)if(e=h.cursor,h.bra=e,h.eq_s(1,"ß"))h.ket=h.cursor,h.slice_from("ss");else{if(e>=h.limit)break;h.cursor=e+1}for(h.cursor=s;;)for(r=h.cursor;;){if(n=h.cursor,h.in_grouping(d,97,252)){if(i=h.cursor,h.bra=i,w("u","U",n))break;if(h.cursor=i,w("y","Y",n))break}if(n>=h.limit)return void(h.cursor=r);h.cursor=n+1}}(),h.cursor=r,function(){s=h.limit,i=s;var r=h.cursor+3;0<=r&&r<=h.limit&&(e=r,f()||((s=h.cursor)=h.limit)return;h.cursor++}}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}),e.Pipeline.registerFunction(e.de.stemmer,"stemmer-de"),e.de.stopWordFilter=e.generateStopWordFilter("aber alle allem allen aller alles als also am an ander andere anderem anderen anderer anderes anderm andern anderr anders auch auf aus bei bin bis bist da damit dann das dasselbe dazu daß dein deine deinem deinen deiner deines dem demselben den denn denselben der derer derselbe derselben des desselben dessen dich die dies diese dieselbe dieselben diesem diesen dieser dieses dir doch dort du durch ein eine einem einen einer eines einig einige einigem einigen einiger einiges einmal er es etwas euch euer eure eurem euren eurer eures für gegen gewesen hab habe haben hat hatte hatten hier hin hinter ich ihm ihn ihnen ihr ihre ihrem ihren ihrer ihres im in indem ins ist jede jedem jeden jeder jedes jene jenem jenen jener jenes jetzt kann kein keine keinem keinen keiner keines können könnte machen man manche manchem manchen mancher manches mein meine meinem meinen meiner meines mich mir mit muss musste nach nicht nichts noch nun nur ob oder ohne sehr sein seine seinem seinen seiner seines selbst sich sie sind so solche solchem solchen solcher solches soll sollte sondern sonst um und uns unse unsem unsen unser unses unter viel vom von vor war waren warst was weg weil weiter welche welchem welchen welcher welches wenn werde werden wie wieder will wir wird wirst wo wollen wollte während würde würden zu zum zur zwar zwischen über".split(" ")),e.Pipeline.registerFunction(e.de.stopWordFilter,"stopWordFilter-de")}}); -------------------------------------------------------------------------------- /docs/assets/javascripts/lunr/lunr.du.js: -------------------------------------------------------------------------------- 1 | !function(r,e){"function"==typeof define&&define.amd?define(e):"object"==typeof exports?module.exports=e():e()(r.lunr)}(this,function(){return function(r){if(void 0===r)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===r.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var e,i,n;r.du=function(){this.pipeline.reset(),this.pipeline.add(r.du.trimmer,r.du.stopWordFilter,r.du.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(r.du.stemmer))},r.du.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",r.du.trimmer=r.trimmerSupport.generateTrimmer(r.du.wordCharacters),r.Pipeline.registerFunction(r.du.trimmer,"trimmer-du"),r.du.stemmer=(e=r.stemmerSupport.Among,i=r.stemmerSupport.SnowballProgram,n=new function(){var r,n,o,t=[new e("",-1,6),new e("á",0,1),new e("ä",0,1),new e("é",0,2),new e("ë",0,2),new e("í",0,3),new e("ï",0,3),new e("ó",0,4),new e("ö",0,4),new e("ú",0,5),new e("ü",0,5)],s=[new e("",-1,3),new e("I",0,2),new e("Y",0,1)],u=[new e("dd",-1,-1),new e("kk",-1,-1),new e("tt",-1,-1)],c=[new e("ene",-1,2),new e("se",-1,3),new e("en",-1,2),new e("heden",2,1),new e("s",-1,3)],a=[new e("end",-1,1),new e("ig",-1,2),new e("ing",-1,1),new e("lijk",-1,3),new e("baar",-1,4),new e("bar",-1,5)],l=[new e("aa",-1,-1),new e("ee",-1,-1),new e("oo",-1,-1),new e("uu",-1,-1)],m=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],d=[1,0,0,17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],f=[17,67,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],_=new i;function w(r){return _.cursor=r,r>=_.limit||(_.cursor++,!1)}function b(){for(;!_.in_grouping(m,97,232);){if(_.cursor>=_.limit)return!0;_.cursor++}for(;!_.out_grouping(m,97,232);){if(_.cursor>=_.limit)return!0;_.cursor++}return!1}function p(){return n<=_.cursor}function g(){return r<=_.cursor}function h(){var r=_.limit-_.cursor;_.find_among_b(u,3)&&(_.cursor=_.limit-r,_.ket=_.cursor,_.cursor>_.limit_backward&&(_.cursor--,_.bra=_.cursor,_.slice_del()))}function k(){var r;o=!1,_.ket=_.cursor,_.eq_s_b(1,"e")&&(_.bra=_.cursor,p()&&(r=_.limit-_.cursor,_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-r,_.slice_del(),o=!0,h())))}function v(){var r;p()&&(r=_.limit-_.cursor,_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-r,_.eq_s_b(3,"gem")||(_.cursor=_.limit-r,_.slice_del(),h())))}this.setCurrent=function(r){_.setCurrent(r)},this.getCurrent=function(){return _.getCurrent()},this.stem=function(){var e=_.cursor;return function(){for(var r,e,i,n=_.cursor;;){if(_.bra=_.cursor,r=_.find_among(t,11))switch(_.ket=_.cursor,r){case 1:_.slice_from("a");continue;case 2:_.slice_from("e");continue;case 3:_.slice_from("i");continue;case 4:_.slice_from("o");continue;case 5:_.slice_from("u");continue;case 6:if(_.cursor>=_.limit)break;_.cursor++;continue}break}for(_.cursor=n,_.bra=n,_.eq_s(1,"y")?(_.ket=_.cursor,_.slice_from("Y")):_.cursor=n;;)if(e=_.cursor,_.in_grouping(m,97,232)){if(i=_.cursor,_.bra=i,_.eq_s(1,"i"))_.ket=_.cursor,_.in_grouping(m,97,232)&&(_.slice_from("I"),_.cursor=e);else if(_.cursor=i,_.eq_s(1,"y"))_.ket=_.cursor,_.slice_from("Y"),_.cursor=e;else if(w(e))break}else if(w(e))break}(),_.cursor=e,n=_.limit,r=n,b()||((n=_.cursor)<3&&(n=3),b()||(r=_.cursor)),_.limit_backward=e,_.cursor=_.limit,function(){var r,e,i,n,t,s,u=_.limit-_.cursor;if(_.ket=_.cursor,r=_.find_among_b(c,5))switch(_.bra=_.cursor,r){case 1:p()&&_.slice_from("heid");break;case 2:v();break;case 3:p()&&_.out_grouping_b(f,97,232)&&_.slice_del()}if(_.cursor=_.limit-u,k(),_.cursor=_.limit-u,_.ket=_.cursor,_.eq_s_b(4,"heid")&&(_.bra=_.cursor,g()&&(e=_.limit-_.cursor,_.eq_s_b(1,"c")||(_.cursor=_.limit-e,_.slice_del(),_.ket=_.cursor,_.eq_s_b(2,"en")&&(_.bra=_.cursor,v())))),_.cursor=_.limit-u,_.ket=_.cursor,r=_.find_among_b(a,6))switch(_.bra=_.cursor,r){case 1:if(g()){if(_.slice_del(),i=_.limit-_.cursor,_.ket=_.cursor,_.eq_s_b(2,"ig")&&(_.bra=_.cursor,g()&&(n=_.limit-_.cursor,!_.eq_s_b(1,"e")))){_.cursor=_.limit-n,_.slice_del();break}_.cursor=_.limit-i,h()}break;case 2:g()&&(t=_.limit-_.cursor,_.eq_s_b(1,"e")||(_.cursor=_.limit-t,_.slice_del()));break;case 3:g()&&(_.slice_del(),k());break;case 4:g()&&_.slice_del();break;case 5:g()&&o&&_.slice_del()}_.cursor=_.limit-u,_.out_grouping_b(d,73,232)&&(s=_.limit-_.cursor,_.find_among_b(l,4)&&_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-s,_.ket=_.cursor,_.cursor>_.limit_backward&&(_.cursor--,_.bra=_.cursor,_.slice_del())))}(),_.cursor=_.limit_backward,function(){for(var r;;)if(_.bra=_.cursor,r=_.find_among(s,3))switch(_.ket=_.cursor,r){case 1:_.slice_from("y");break;case 2:_.slice_from("i");break;case 3:if(_.cursor>=_.limit)return;_.cursor++}}(),!0}},function(r){return"function"==typeof r.update?r.update(function(r){return n.setCurrent(r),n.stem(),n.getCurrent()}):(n.setCurrent(r),n.stem(),n.getCurrent())}),r.Pipeline.registerFunction(r.du.stemmer,"stemmer-du"),r.du.stopWordFilter=r.generateStopWordFilter(" aan al alles als altijd andere ben bij daar dan dat de der deze die dit doch doen door dus een eens en er ge geen geweest haar had heb hebben heeft hem het hier hij hoe hun iemand iets ik in is ja je kan kon kunnen maar me meer men met mij mijn moet na naar niet niets nog nu of om omdat onder ons ook op over reeds te tegen toch toen tot u uit uw van veel voor want waren was wat werd wezen wie wil worden wordt zal ze zelf zich zij zijn zo zonder zou".split(" ")),r.Pipeline.registerFunction(r.du.stopWordFilter,"stopWordFilter-du")}}); -------------------------------------------------------------------------------- /docs/assets/javascripts/lunr/lunr.ja.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(m){if(void 0===m)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===m.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var l="2"==m.version[0];m.ja=function(){this.pipeline.reset(),this.pipeline.add(m.ja.trimmer,m.ja.stopWordFilter,m.ja.stemmer),l?this.tokenizer=m.ja.tokenizer:(m.tokenizer&&(m.tokenizer=m.ja.tokenizer),this.tokenizerFn&&(this.tokenizerFn=m.ja.tokenizer))};var j=new m.TinySegmenter;m.ja.tokenizer=function(e){var r,t,i,n,o,s,p,a,u;if(!arguments.length||null==e||null==e)return[];if(Array.isArray(e))return e.map(function(e){return l?new m.Token(e.toLowerCase()):e.toLowerCase()});for(r=(t=e.toString().toLowerCase().replace(/^\s+/,"")).length-1;0<=r;r--)if(/\S/.test(t.charAt(r))){t=t.substring(0,r+1);break}for(o=[],i=t.length,p=a=0;a<=i;a++)if(s=a-p,t.charAt(a).match(/\s/)||a==i){if(0=0;o--)if(/\S/.test(i.charAt(o))){i=i.substring(0,o+1);break}return t.segment(i).filter(function(e){return!!e}).map(function(t){return r?new e.Token(t):t})},e.jp.stemmer=function(e){return e},e.Pipeline.registerFunction(e.jp.stemmer,"stemmer-jp"),e.jp.wordCharacters="一二三四五六七八九十百千万億兆一-龠々〆ヵヶぁ-んァ-ヴーア-ン゙a-zA-Za-zA-Z0-90-9",e.jp.stopWordFilter=function(t){if(-1===e.jp.stopWordFilter.stopWords.indexOf(r?t.toString():t))return t},e.jp.stopWordFilter=e.generateStopWordFilter("これ それ あれ この その あの ここ そこ あそこ こちら どこ だれ なに なん 何 私 貴方 貴方方 我々 私達 あの人 あのかた 彼女 彼 です あります おります います は が の に を で え から まで より も どの と し それで しかし".split(" ")),e.Pipeline.registerFunction(e.jp.stopWordFilter,"stopWordFilter-jp")}}); -------------------------------------------------------------------------------- /docs/assets/javascripts/lunr/lunr.multi.js: -------------------------------------------------------------------------------- 1 | !function(e,i){"function"==typeof define&&define.amd?define(i):"object"==typeof exports?module.exports=i():i()(e.lunr)}(this,function(){return function(e){e.multiLanguage=function(){for(var i=Array.prototype.slice.call(arguments),t=i.join("-"),r="",n=[],s=[],p=0;p=_.limit||(_.cursor++,!1)}function w(){for(;!_.in_grouping(m,97,232);){if(_.cursor>=_.limit)return!0;_.cursor++}for(;!_.out_grouping(m,97,232);){if(_.cursor>=_.limit)return!0;_.cursor++}return!1}function b(){return i<=_.cursor}function p(){return e<=_.cursor}function g(){var r=_.limit-_.cursor;_.find_among_b(t,3)&&(_.cursor=_.limit-r,_.ket=_.cursor,_.cursor>_.limit_backward&&(_.cursor--,_.bra=_.cursor,_.slice_del()))}function h(){var r;u=!1,_.ket=_.cursor,_.eq_s_b(1,"e")&&(_.bra=_.cursor,b()&&(r=_.limit-_.cursor,_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-r,_.slice_del(),u=!0,g())))}function k(){var r;b()&&(r=_.limit-_.cursor,_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-r,_.eq_s_b(3,"gem")||(_.cursor=_.limit-r,_.slice_del(),g())))}this.setCurrent=function(r){_.setCurrent(r)},this.getCurrent=function(){return _.getCurrent()},this.stem=function(){var r=_.cursor;return function(){for(var r,e,i,n=_.cursor;;){if(_.bra=_.cursor,r=_.find_among(o,11))switch(_.ket=_.cursor,r){case 1:_.slice_from("a");continue;case 2:_.slice_from("e");continue;case 3:_.slice_from("i");continue;case 4:_.slice_from("o");continue;case 5:_.slice_from("u");continue;case 6:if(_.cursor>=_.limit)break;_.cursor++;continue}break}for(_.cursor=n,_.bra=n,_.eq_s(1,"y")?(_.ket=_.cursor,_.slice_from("Y")):_.cursor=n;;)if(e=_.cursor,_.in_grouping(m,97,232)){if(i=_.cursor,_.bra=i,_.eq_s(1,"i"))_.ket=_.cursor,_.in_grouping(m,97,232)&&(_.slice_from("I"),_.cursor=e);else if(_.cursor=i,_.eq_s(1,"y"))_.ket=_.cursor,_.slice_from("Y"),_.cursor=e;else if(s(e))break}else if(s(e))break}(),_.cursor=r,i=_.limit,e=i,w()||((i=_.cursor)<3&&(i=3),w()||(e=_.cursor)),_.limit_backward=r,_.cursor=_.limit,function(){var r,e,i,n,o,t,s=_.limit-_.cursor;if(_.ket=_.cursor,r=_.find_among_b(c,5))switch(_.bra=_.cursor,r){case 1:b()&&_.slice_from("heid");break;case 2:k();break;case 3:b()&&_.out_grouping_b(f,97,232)&&_.slice_del()}if(_.cursor=_.limit-s,h(),_.cursor=_.limit-s,_.ket=_.cursor,_.eq_s_b(4,"heid")&&(_.bra=_.cursor,p()&&(e=_.limit-_.cursor,_.eq_s_b(1,"c")||(_.cursor=_.limit-e,_.slice_del(),_.ket=_.cursor,_.eq_s_b(2,"en")&&(_.bra=_.cursor,k())))),_.cursor=_.limit-s,_.ket=_.cursor,r=_.find_among_b(a,6))switch(_.bra=_.cursor,r){case 1:if(p()){if(_.slice_del(),i=_.limit-_.cursor,_.ket=_.cursor,_.eq_s_b(2,"ig")&&(_.bra=_.cursor,p()&&(n=_.limit-_.cursor,!_.eq_s_b(1,"e")))){_.cursor=_.limit-n,_.slice_del();break}_.cursor=_.limit-i,g()}break;case 2:p()&&(o=_.limit-_.cursor,_.eq_s_b(1,"e")||(_.cursor=_.limit-o,_.slice_del()));break;case 3:p()&&(_.slice_del(),h());break;case 4:p()&&_.slice_del();break;case 5:p()&&u&&_.slice_del()}_.cursor=_.limit-s,_.out_grouping_b(d,73,232)&&(t=_.limit-_.cursor,_.find_among_b(l,4)&&_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-t,_.ket=_.cursor,_.cursor>_.limit_backward&&(_.cursor--,_.bra=_.cursor,_.slice_del())))}(),_.cursor=_.limit_backward,function(){for(var r;;)if(_.bra=_.cursor,r=_.find_among(n,3))switch(_.ket=_.cursor,r){case 1:_.slice_from("y");break;case 2:_.slice_from("i");break;case 3:if(_.cursor>=_.limit)return;_.cursor++}}(),!0}},function(r){return"function"==typeof r.update?r.update(function(r){return e.setCurrent(r),e.stem(),e.getCurrent()}):(e.setCurrent(r),e.stem(),e.getCurrent())}),r.Pipeline.registerFunction(r.nl.stemmer,"stemmer-nl"),r.nl.stopWordFilter=r.generateStopWordFilter(" aan al alles als altijd andere ben bij daar dan dat de der deze die dit doch doen door dus een eens en er ge geen geweest haar had heb hebben heeft hem het hier hij hoe hun iemand iets ik in is ja je kan kon kunnen maar me meer men met mij mijn moet na naar niet niets nog nu of om omdat onder ons ook op over reeds te tegen toch toen tot u uit uw van veel voor want waren was wat werd wezen wie wil worden wordt zal ze zelf zich zij zijn zo zonder zou".split(" ")),r.Pipeline.registerFunction(r.nl.stopWordFilter,"stopWordFilter-nl")}}); -------------------------------------------------------------------------------- /docs/assets/javascripts/lunr/lunr.no.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r,n,i;e.no=function(){this.pipeline.reset(),this.pipeline.add(e.no.trimmer,e.no.stopWordFilter,e.no.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.no.stemmer))},e.no.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.no.trimmer=e.trimmerSupport.generateTrimmer(e.no.wordCharacters),e.Pipeline.registerFunction(e.no.trimmer,"trimmer-no"),e.no.stemmer=(r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){var e,i,t=[new r("a",-1,1),new r("e",-1,1),new r("ede",1,1),new r("ande",1,1),new r("ende",1,1),new r("ane",1,1),new r("ene",1,1),new r("hetene",6,1),new r("erte",1,3),new r("en",-1,1),new r("heten",9,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",12,1),new r("s",-1,2),new r("as",14,1),new r("es",14,1),new r("edes",16,1),new r("endes",16,1),new r("enes",16,1),new r("hetenes",19,1),new r("ens",14,1),new r("hetens",21,1),new r("ers",14,1),new r("ets",14,1),new r("et",-1,1),new r("het",25,1),new r("ert",-1,3),new r("ast",-1,1)],o=[new r("dt",-1,-1),new r("vt",-1,-1)],s=[new r("leg",-1,1),new r("eleg",0,1),new r("ig",-1,1),new r("eig",2,1),new r("lig",2,1),new r("elig",4,1),new r("els",-1,1),new r("lov",-1,1),new r("elov",7,1),new r("slov",7,1),new r("hetslov",9,1)],a=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],m=[119,125,149,1],l=new n;this.setCurrent=function(e){l.setCurrent(e)},this.getCurrent=function(){return l.getCurrent()},this.stem=function(){var r,n,u,d,c=l.cursor;return function(){var r,n=l.cursor+3;if(i=l.limit,0<=n||n<=l.limit){for(e=n;;){if(r=l.cursor,l.in_grouping(a,97,248)){l.cursor=r;break}if(r>=l.limit)return;l.cursor=r+1}for(;!l.out_grouping(a,97,248);){if(l.cursor>=l.limit)return;l.cursor++}(i=l.cursor)=i&&(r=l.limit_backward,l.limit_backward=i,l.ket=l.cursor,e=l.find_among_b(t,29),l.limit_backward=r,e))switch(l.bra=l.cursor,e){case 1:l.slice_del();break;case 2:n=l.limit-l.cursor,l.in_grouping_b(m,98,122)?l.slice_del():(l.cursor=l.limit-n,l.eq_s_b(1,"k")&&l.out_grouping_b(a,97,248)&&l.slice_del());break;case 3:l.slice_from("er")}}(),l.cursor=l.limit,n=l.limit-l.cursor,l.cursor>=i&&(r=l.limit_backward,l.limit_backward=i,l.ket=l.cursor,l.find_among_b(o,2)?(l.bra=l.cursor,l.limit_backward=r,l.cursor=l.limit-n,l.cursor>l.limit_backward&&(l.cursor--,l.bra=l.cursor,l.slice_del())):l.limit_backward=r),l.cursor=l.limit,l.cursor>=i&&(d=l.limit_backward,l.limit_backward=i,l.ket=l.cursor,(u=l.find_among_b(s,11))?(l.bra=l.cursor,l.limit_backward=d,1==u&&l.slice_del()):l.limit_backward=d),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}),e.Pipeline.registerFunction(e.no.stemmer,"stemmer-no"),e.no.stopWordFilter=e.generateStopWordFilter("alle at av bare begge ble blei bli blir blitt både båe da de deg dei deim deira deires dem den denne der dere deres det dette di din disse ditt du dykk dykkar då eg ein eit eitt eller elles en enn er et ett etter for fordi fra før ha hadde han hans har hennar henne hennes her hjå ho hoe honom hoss hossen hun hva hvem hver hvilke hvilken hvis hvor hvordan hvorfor i ikke ikkje ikkje ingen ingi inkje inn inni ja jeg kan kom korleis korso kun kunne kva kvar kvarhelst kven kvi kvifor man mange me med medan meg meget mellom men mi min mine mitt mot mykje ned no noe noen noka noko nokon nokor nokre nå når og også om opp oss over på samme seg selv si si sia sidan siden sin sine sitt sjøl skal skulle slik so som som somme somt så sånn til um upp ut uten var vart varte ved vere verte vi vil ville vore vors vort vår være være vært å".split(" ")),e.Pipeline.registerFunction(e.no.stopWordFilter,"stopWordFilter-no")}}); -------------------------------------------------------------------------------- /docs/assets/javascripts/lunr/lunr.ru.js: -------------------------------------------------------------------------------- 1 | !function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n():n()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var n,r,t;e.ru=function(){this.pipeline.reset(),this.pipeline.add(e.ru.trimmer,e.ru.stopWordFilter,e.ru.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ru.stemmer))},e.ru.wordCharacters="Ѐ-҄҇-ԯᴫᵸⷠ-ⷿꙀ-ꚟ︮︯",e.ru.trimmer=e.trimmerSupport.generateTrimmer(e.ru.wordCharacters),e.Pipeline.registerFunction(e.ru.trimmer,"trimmer-ru"),e.ru.stemmer=(n=e.stemmerSupport.Among,r=e.stemmerSupport.SnowballProgram,t=new function(){var e,t,w=[new n("в",-1,1),new n("ив",0,2),new n("ыв",0,2),new n("вши",-1,1),new n("ивши",3,2),new n("ывши",3,2),new n("вшись",-1,1),new n("ившись",6,2),new n("ывшись",6,2)],i=[new n("ее",-1,1),new n("ие",-1,1),new n("ое",-1,1),new n("ые",-1,1),new n("ими",-1,1),new n("ыми",-1,1),new n("ей",-1,1),new n("ий",-1,1),new n("ой",-1,1),new n("ый",-1,1),new n("ем",-1,1),new n("им",-1,1),new n("ом",-1,1),new n("ым",-1,1),new n("его",-1,1),new n("ого",-1,1),new n("ему",-1,1),new n("ому",-1,1),new n("их",-1,1),new n("ых",-1,1),new n("ею",-1,1),new n("ою",-1,1),new n("ую",-1,1),new n("юю",-1,1),new n("ая",-1,1),new n("яя",-1,1)],u=[new n("ем",-1,1),new n("нн",-1,1),new n("вш",-1,1),new n("ивш",2,2),new n("ывш",2,2),new n("щ",-1,1),new n("ющ",5,1),new n("ующ",6,2)],s=[new n("сь",-1,1),new n("ся",-1,1)],o=[new n("ла",-1,1),new n("ила",0,2),new n("ыла",0,2),new n("на",-1,1),new n("ена",3,2),new n("ете",-1,1),new n("ите",-1,2),new n("йте",-1,1),new n("ейте",7,2),new n("уйте",7,2),new n("ли",-1,1),new n("или",10,2),new n("ыли",10,2),new n("й",-1,1),new n("ей",13,2),new n("уй",13,2),new n("л",-1,1),new n("ил",16,2),new n("ыл",16,2),new n("ем",-1,1),new n("им",-1,2),new n("ым",-1,2),new n("н",-1,1),new n("ен",22,2),new n("ло",-1,1),new n("ило",24,2),new n("ыло",24,2),new n("но",-1,1),new n("ено",27,2),new n("нно",27,1),new n("ет",-1,1),new n("ует",30,2),new n("ит",-1,2),new n("ыт",-1,2),new n("ют",-1,1),new n("уют",34,2),new n("ят",-1,2),new n("ны",-1,1),new n("ены",37,2),new n("ть",-1,1),new n("ить",39,2),new n("ыть",39,2),new n("ешь",-1,1),new n("ишь",-1,2),new n("ю",-1,2),new n("ую",44,2)],c=[new n("а",-1,1),new n("ев",-1,1),new n("ов",-1,1),new n("е",-1,1),new n("ие",3,1),new n("ье",3,1),new n("и",-1,1),new n("еи",6,1),new n("ии",6,1),new n("ами",6,1),new n("ями",6,1),new n("иями",10,1),new n("й",-1,1),new n("ей",12,1),new n("ией",13,1),new n("ий",12,1),new n("ой",12,1),new n("ам",-1,1),new n("ем",-1,1),new n("ием",18,1),new n("ом",-1,1),new n("ям",-1,1),new n("иям",21,1),new n("о",-1,1),new n("у",-1,1),new n("ах",-1,1),new n("ях",-1,1),new n("иях",26,1),new n("ы",-1,1),new n("ь",-1,1),new n("ю",-1,1),new n("ию",30,1),new n("ью",30,1),new n("я",-1,1),new n("ия",33,1),new n("ья",33,1)],m=[new n("ост",-1,1),new n("ость",-1,1)],l=[new n("ейше",-1,1),new n("н",-1,2),new n("ейш",-1,1),new n("ь",-1,3)],f=[33,65,8,232],a=new r;function p(){for(;!a.in_grouping(f,1072,1103);){if(a.cursor>=a.limit)return!1;a.cursor++}return!0}function d(){for(;!a.out_grouping(f,1072,1103);){if(a.cursor>=a.limit)return!1;a.cursor++}return!0}function _(e,n){var r,t;if(a.ket=a.cursor,r=a.find_among_b(e,n)){switch(a.bra=a.cursor,r){case 1:if(t=a.limit-a.cursor,!a.eq_s_b(1,"а")&&(a.cursor=a.limit-t,!a.eq_s_b(1,"я")))return!1;case 2:a.slice_del()}return!0}return!1}function b(e,n){var r;return a.ket=a.cursor,!!(r=a.find_among_b(e,n))&&(a.bra=a.cursor,1==r&&a.slice_del(),!0)}function h(){return!!b(i,26)&&(_(u,8),!0)}function g(){var n;a.ket=a.cursor,(n=a.find_among_b(m,2))&&(a.bra=a.cursor,e<=a.cursor&&1==n&&a.slice_del())}this.setCurrent=function(e){a.setCurrent(e)},this.getCurrent=function(){return a.getCurrent()},this.stem=function(){return t=a.limit,e=t,p()&&(t=a.cursor,d()&&p()&&d()&&(e=a.cursor)),a.cursor=a.limit,!(a.cursor=i&&t[(e-=i)>>3]&1<<(7&e))return this.cursor++,!0}return!1},in_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e<=s&&e>=i&&t[(e-=i)>>3]&1<<(7&e))return this.cursor--,!0}return!1},out_grouping:function(t,i,s){if(this.cursors||e>3]&1<<(7&e)))return this.cursor++,!0}return!1},out_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e>s||e>3]&1<<(7&e)))return this.cursor--,!0}return!1},eq_s:function(t,i){if(this.limit-this.cursor>1),f=0,l=o0||e==s||c)break;c=!0}}for(;;){if(o>=(_=t[s]).s_size){if(this.cursor=n+_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n+_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},find_among_b:function(t,i){for(var s=0,e=i,n=this.cursor,u=this.limit_backward,o=0,h=0,c=!1;;){for(var a=s+(e-s>>1),f=0,l=o=0;_--){if(n-l==u){f=-1;break}if(f=r.charCodeAt(n-1-l)-m.s[_])break;l++}if(f<0?(e=a,h=l):(s=a,o=l),e-s<=1){if(s>0||e==s||c)break;c=!0}}for(;;){var m;if(o>=(m=t[s]).s_size){if(this.cursor=n-m.s_size,!m.method)return m.result;var b=m.method();if(this.cursor=n-m.s_size,b)return m.result}if((s=m.substring_i)<0)return 0}},replace_s:function(t,i,s){var e=s.length-(i-t),n=r.substring(0,t),u=r.substring(i);return r=n+s+u,this.limit+=e,this.cursor>=i?this.cursor+=e:this.cursor>t&&(this.cursor=t),e},slice_check:function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>r.length)throw"faulty slice operation"},slice_from:function(r){this.slice_check(),this.replace_s(this.bra,this.ket,r)},slice_del:function(){this.slice_from("")},insert:function(r,t,i){var s=this.replace_s(r,t,i);r<=this.bra&&(this.bra+=s),r<=this.ket&&(this.ket+=s)},slice_to:function(){return this.slice_check(),r.substring(this.bra,this.ket)},eq_v_b:function(r){return this.eq_s_b(r.length,r)}}}},r.trimmerSupport={generateTrimmer:function(r){var t=new RegExp("^[^"+r+"]+"),i=new RegExp("[^"+r+"]+$");return function(r){return"function"==typeof r.update?r.update(function(r){return r.replace(t,"").replace(i,"")}):r.replace(t,"").replace(i,"")}}}}}); -------------------------------------------------------------------------------- /docs/assets/javascripts/lunr/lunr.sv.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r,n,t;e.sv=function(){this.pipeline.reset(),this.pipeline.add(e.sv.trimmer,e.sv.stopWordFilter,e.sv.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sv.stemmer))},e.sv.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.sv.trimmer=e.trimmerSupport.generateTrimmer(e.sv.wordCharacters),e.Pipeline.registerFunction(e.sv.trimmer,"trimmer-sv"),e.sv.stemmer=(r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,t=new function(){var e,t,i=[new r("a",-1,1),new r("arna",0,1),new r("erna",0,1),new r("heterna",2,1),new r("orna",0,1),new r("ad",-1,1),new r("e",-1,1),new r("ade",6,1),new r("ande",6,1),new r("arne",6,1),new r("are",6,1),new r("aste",6,1),new r("en",-1,1),new r("anden",12,1),new r("aren",12,1),new r("heten",12,1),new r("ern",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",18,1),new r("or",-1,1),new r("s",-1,2),new r("as",21,1),new r("arnas",22,1),new r("ernas",22,1),new r("ornas",22,1),new r("es",21,1),new r("ades",26,1),new r("andes",26,1),new r("ens",21,1),new r("arens",29,1),new r("hetens",29,1),new r("erns",21,1),new r("at",-1,1),new r("andet",-1,1),new r("het",-1,1),new r("ast",-1,1)],s=[new r("dd",-1,-1),new r("gd",-1,-1),new r("nn",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1),new r("tt",-1,-1)],a=[new r("ig",-1,1),new r("lig",0,1),new r("els",-1,1),new r("fullt",-1,3),new r("löst",-1,2)],o=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,24,0,32],u=[119,127,149],m=new n;this.setCurrent=function(e){m.setCurrent(e)},this.getCurrent=function(){return m.getCurrent()},this.stem=function(){var r,n=m.cursor;return function(){var r,n=m.cursor+3;if(t=m.limit,0<=n||n<=m.limit){for(e=n;;){if(r=m.cursor,m.in_grouping(o,97,246)){m.cursor=r;break}if(m.cursor=r,m.cursor>=m.limit)return;m.cursor++}for(;!m.out_grouping(o,97,246);){if(m.cursor>=m.limit)return;m.cursor++}(t=m.cursor)=t&&(m.limit_backward=t,m.cursor=m.limit,m.ket=m.cursor,e=m.find_among_b(i,37),m.limit_backward=r,e))switch(m.bra=m.cursor,e){case 1:m.slice_del();break;case 2:m.in_grouping_b(u,98,121)&&m.slice_del()}}(),m.cursor=m.limit,r=m.limit_backward,m.cursor>=t&&(m.limit_backward=t,m.cursor=m.limit,m.find_among_b(s,7)&&(m.cursor=m.limit,m.ket=m.cursor,m.cursor>m.limit_backward&&(m.bra=--m.cursor,m.slice_del())),m.limit_backward=r),m.cursor=m.limit,function(){var e,r;if(m.cursor>=t){if(r=m.limit_backward,m.limit_backward=t,m.cursor=m.limit,m.ket=m.cursor,e=m.find_among_b(a,5))switch(m.bra=m.cursor,e){case 1:m.slice_del();break;case 2:m.slice_from("lös");break;case 3:m.slice_from("full")}m.limit_backward=r}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return t.setCurrent(e),t.stem(),t.getCurrent()}):(t.setCurrent(e),t.stem(),t.getCurrent())}),e.Pipeline.registerFunction(e.sv.stemmer,"stemmer-sv"),e.sv.stopWordFilter=e.generateStopWordFilter("alla allt att av blev bli blir blivit de dem den denna deras dess dessa det detta dig din dina ditt du där då efter ej eller en er era ert ett från för ha hade han hans har henne hennes hon honom hur här i icke ingen inom inte jag ju kan kunde man med mellan men mig min mina mitt mot mycket ni nu när någon något några och om oss på samma sedan sig sin sina sitta själv skulle som så sådan sådana sådant till under upp ut utan vad var vara varför varit varje vars vart vem vi vid vilka vilkas vilken vilket vår våra vårt än är åt över".split(" ")),e.Pipeline.registerFunction(e.sv.stopWordFilter,"stopWordFilter-sv")}}); -------------------------------------------------------------------------------- /docs/assets/javascripts/lunr/lunr.th.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(t){if(void 0===t)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===t.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var i="2"==t.version[0];t.th=function(){this.pipeline.reset(),this.pipeline.add(t.th.trimmer),i?this.tokenizer=t.th.tokenizer:(t.tokenizer&&(t.tokenizer=t.th.tokenizer),this.tokenizerFn&&(this.tokenizerFn=t.th.tokenizer))},t.th.wordCharacters="[฀-๿]",t.th.trimmer=t.trimmerSupport.generateTrimmer(t.th.wordCharacters),t.Pipeline.registerFunction(t.th.trimmer,"trimmer-th");var n=t.wordcut;n.init(),t.th.tokenizer=function(e){if(!arguments.length||null==e||null==e)return[];if(Array.isArray(e))return e.map(function(e){return i?new t.Token(e):e});var r=e.toString().replace(/^\s+/,"");return n.cut(r).split("|")}}}); -------------------------------------------------------------------------------- /docs/assets/javascripts/modernizr.01ccdecf.js: -------------------------------------------------------------------------------- 1 | !function(e,t){for(var n in t)e[n]=t[n]}(window,function(n){var r={};function o(e){if(r[e])return r[e].exports;var t=r[e]={i:e,l:!1,exports:{}};return n[e].call(t.exports,t,t.exports,o),t.l=!0,t.exports}return o.m=n,o.c=r,o.d=function(e,t,n){o.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(t,e){if(1&e&&(t=o(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(o.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)o.d(n,r,function(e){return t[e]}.bind(null,r));return n},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="",o(o.s=12)}({12:function(e,t,n){"use strict";n.r(t);n(13)},13:function(e,t){var n;(function(i,d,p){function y(e,t){return typeof e===t}function s(e){var t=S.className,n=c._config.classPrefix||"";if(b&&(t=t.baseVal),c._config.enableJSClass){var r=new RegExp("(^|\\s)"+n+"no-js(\\s|$)");t=t.replace(r,"$1"+n+"js$2")}c._config.enableClasses&&(0c;c++)if(m=e[c],v=j.style[m],l(m,"-")&&(m=p(m)),j.style[m]!==n){if(i||r(o,"undefined"))return a(),"pfx"!=t||m;try{j.style[m]=o}catch(e){}if(j.style[m]!=v)return a(),"pfx"!=t||m}return a(),!1}function m(e,t){return function(){return e.apply(t,arguments)}}function v(e,t,n){var o;for(var i in e)if(e[i]in t)return!1===n?e[i]:(o=t[e[i]],r(o,"function")?m(o,n||t):o);return!1}function g(e,t,n,o,i){var s=e.charAt(0).toUpperCase()+e.slice(1),a=(e+" "+k.join(s+" ")+s).split(" ");return r(t,"string")||r(t,"undefined")?h(a,t,o,i):(a=(e+" "+A.join(s+" ")+s).split(" "),v(a,t,n))}function y(e,t,r){return g(e,n,n,t,r)}var w=[],S={_version:"3.5.0",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(e,t){var n=this;setTimeout(function(){t(n[e])},0)},addTest:function(e,t,n){w.push({name:e,fn:t,options:n})},addAsyncTest:function(e){w.push({name:null,fn:e})}},C=function(){};C.prototype=S,C=new C;var b,x=[],_=t.documentElement,T="svg"===_.nodeName.toLowerCase();!function(){var e={}.hasOwnProperty;b=r(e,"undefined")||r(e.call,"undefined")?function(e,t){return t in e&&r(e.constructor.prototype[t],"undefined")}:function(t,n){return e.call(t,n)}}(),S._l={},S.on=function(e,t){this._l[e]||(this._l[e]=[]),this._l[e].push(t),C.hasOwnProperty(e)&&setTimeout(function(){C._trigger(e,C[e])},0)},S._trigger=function(e,t){if(this._l[e]){var n=this._l[e];setTimeout(function(){var e;for(e=0;e () => true 9 | } 10 | } 11 | } 12 | } 13 | } 14 | 15 | window.onload = function() { 16 | const ui = SwaggerUIBundle({ 17 | url: "https://raw.githubusercontent.com/datosgobar/georef-ar-api/master/docs/open-api/spec/openapi.json", 18 | dom_id: '#swagger-ui', 19 | deepLinking: true, 20 | presets: [ 21 | SwaggerUIBundle.presets.apis 22 | ], 23 | plugins: [ 24 | DisableTryItOutPlugin 25 | ], 26 | defaultModelsExpandDepth: -1 // Remover modelos 27 | }) 28 | 29 | window.ui = ui 30 | } 31 | -------------------------------------------------------------------------------- /docs/open-api/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var path = require('path'); 3 | 4 | gulp.task('build', function() { 5 | // Copiar archivos de swagger-ui 6 | var swagger_dir = path.join('node_modules', 'swagger-ui-dist'); 7 | var globs = [ 8 | path.join(swagger_dir, '*.html'), 9 | path.join(swagger_dir, '*.js'), 10 | path.join(swagger_dir, '*.css') 11 | ]; 12 | 13 | gulp.src(globs).pipe(gulp.dest('.')); 14 | 15 | // Copiar archivos de swagger-ui-themes 16 | gulp.src(path.join('node_modules', 'swagger-ui-themes', 'themes', '3.x', 17 | '*.css')).pipe(gulp.dest('.')) 18 | 19 | // Copiar archivos propios de /src 20 | gulp.src(path.join('src', '**')).pipe(gulp.dest('.')); 21 | }); -------------------------------------------------------------------------------- /docs/open-api/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | georef 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 35 | 36 | 37 | 38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /docs/open-api/index.js: -------------------------------------------------------------------------------- 1 | try { 2 | module.exports.SwaggerUIBundle = require("./swagger-ui-bundle.js") 3 | module.exports.SwaggerUIStandalonePreset = require("./swagger-ui-standalone-preset.js") 4 | } catch(e) { 5 | // swallow the error if there's a problem loading the assets. 6 | // allows this module to support providing the assets for browserish contexts, 7 | // without exploding in a Node context. 8 | // 9 | // see https://github.com/swagger-api/swagger-ui/issues/3291#issuecomment-311195388 10 | // for more information. 11 | } 12 | 13 | // `absolutePath` and `getAbsoluteFSPath` are both here because at one point, 14 | // we documented having one and actually implemented the other. 15 | // They were both retained so we don't break anyone's code. 16 | module.exports.absolutePath = require("./absolute-path.js") 17 | module.exports.getAbsoluteFSPath = require("./absolute-path.js") 18 | -------------------------------------------------------------------------------- /docs/open-api/oauth2-redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 68 | -------------------------------------------------------------------------------- /docs/open-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "georef-api-docs", 3 | "version": "1.0.0", 4 | "description": "API del servicio de normalización y geocodificación para direcciones de Argentina.", 5 | "directories": { 6 | "doc": "docs" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/datosgobar/georef-api.git" 11 | }, 12 | "author": "datosgobar", 13 | "license": "ISC", 14 | "bugs": { 15 | "url": "https://github.com/datosgobar/georef-api/issues" 16 | }, 17 | "homepage": "https://github.com/datosgobar/georef-api#readme", 18 | "dependencies": { 19 | "swagger-ui-dist": "^3.17.5", 20 | "swagger-ui-themes": "^3.0.0" 21 | }, 22 | "devDependencies": { 23 | "gulp": "^3.9.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docs/open-api/src/georef-docs.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const DisableTryItOutPlugin = function() { 4 | return { 5 | statePlugins: { 6 | spec: { 7 | wrapSelectors: { 8 | allowTryItOutFor: () => () => false 9 | } 10 | } 11 | } 12 | } 13 | } 14 | 15 | window.onload = function() { 16 | const ui = SwaggerUIBundle({ 17 | url: "https://raw.githubusercontent.com/datosgobar/georef-ar-api/master/docs/open-api/spec/openapi.json", 18 | dom_id: '#swagger-ui', 19 | deepLinking: true, 20 | presets: [ 21 | SwaggerUIBundle.presets.apis 22 | ], 23 | plugins: [ 24 | DisableTryItOutPlugin 25 | ], 26 | defaultModelsExpandDepth: -1 // Remover modelos 27 | }) 28 | 29 | window.ui = ui 30 | } 31 | -------------------------------------------------------------------------------- /docs/open-api/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | georef 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 35 | 36 | 37 | 38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /docs/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | http://apis.datos.gob.ar/georef/ 5 | 2019-07-21 6 | daily 7 | 8 | 9 | http://apis.datos.gob.ar/georef/quick-start/ 10 | 2019-07-21 11 | daily 12 | 13 | 14 | http://apis.datos.gob.ar/georef/bulk/ 15 | 2019-07-21 16 | daily 17 | 18 | 19 | http://apis.datos.gob.ar/georef/addresses/ 20 | 2019-07-21 21 | daily 22 | 23 | 24 | http://apis.datos.gob.ar/georef/geom-operations/ 25 | 2019-07-21 26 | daily 27 | 28 | 29 | http://apis.datos.gob.ar/georef/shapefiles/ 30 | 2019-07-21 31 | daily 32 | 33 | 34 | http://apis.datos.gob.ar/georef/download/ 35 | 2019-07-21 36 | daily 37 | 38 | 39 | http://apis.datos.gob.ar/georef/spreadsheet-integration/ 40 | 2019-07-21 41 | daily 42 | 43 | 44 | http://apis.datos.gob.ar/georef/python-usage/ 45 | 2019-07-21 46 | daily 47 | 48 | 49 | http://apis.datos.gob.ar/georef/jwt-token/ 50 | 2019-07-21 51 | daily 52 | 53 | 54 | 55 | 56 | daily 57 | 58 | 59 | http://apis.datos.gob.ar/georef/communes/ 60 | 2019-07-21 61 | daily 62 | 63 | 64 | http://apis.datos.gob.ar/georef/localities/ 65 | 2019-07-21 66 | daily 67 | 68 | 69 | http://apis.datos.gob.ar/georef/terms/ 70 | 2019-07-21 71 | daily 72 | 73 | 74 | http://apis.datos.gob.ar/georef/applications/ 75 | 2019-07-21 76 | daily 77 | 78 | 79 | http://apis.datos.gob.ar/georef/georef-api-development/ 80 | 2019-07-21 81 | daily 82 | 83 | 84 | http://apis.datos.gob.ar/georef/deploy/ 85 | 2019-07-21 86 | daily 87 | 88 | 89 | http://apis.datos.gob.ar/georef/history/ 90 | 2019-07-21 91 | daily 92 | 93 | 94 | http://apis.datos.gob.ar/georef/python3.6/ 95 | 2019-07-21 96 | daily 97 | 98 | 99 | http://apis.datos.gob.ar/georef/etl/ 100 | 2019-07-21 101 | daily 102 | 103 | 104 | http://apis.datos.gob.ar/georef/etl-data/ 105 | 2019-07-21 106 | daily 107 | 108 | 109 | http://apis.datos.gob.ar/georef/etl-install/ 110 | 2019-07-21 111 | daily 112 | 113 | -------------------------------------------------------------------------------- /docs/sitemap.xml.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/sitemap.xml.gz -------------------------------------------------------------------------------- /docs/src/addresses.md: -------------------------------------------------------------------------------- 1 | # Normalización de Direcciones 2 | 3 | El recurso `/direcciones` de la API permite normalizar y georreferenciar direcciones de calles. Como todos los otros recursos, cuenta con varios filtros y opciones que permiten controlar los resultados obtenidos. 4 | 5 | ## Parámetros y filtros 6 | 7 | El único parámetro obligatorio del recurso `/direcciones` es `direccion`. El mismo debe tomar el valor de una dirección: es decir, una combinación de nombres de calles y una altura preferiblemente numérica. La API tolera direcciones con distintas estructuras, y se hace un esfuerzo en intentar interpretar qué información representa cada parte del valor recibido, teniendo en cuenta errores de escritura comunes. Para lograr esto, se utiliza la librería [georef-ar-address](https://github.com/datosgobar/georef-ar-address). En algunos casos, la estructura de la dirección no puede ser interpretada correctamente; para evitar estos casos se recomienda utilizar direcciones con el siguiente formato aproximado: 8 | 9 | - **calle** [altura] 10 | - **calle 1** [altura] *esquina/y* **calle 2** 11 | - **calle 1** *esquina/y* **calle 2** [altura] 12 | - **calle 1** [altura] *entre* **calle 2** y **calle 3** 13 | - **calle 1** *entre* **calle 2** y **calle 3** [altura] 14 | 15 | En todos los casos, el valor [altura] es opcional, y de estar presente puede ser seguido de un piso/número de departamento. 16 | 17 | El resto de los parámetros aceptados por el recurso `/direccion` están listados en la [referencia completa de la API](https://datosgobar.github.io/georef-ar-api/open-api). Se recomienda utilizar los parámetros `provincia`, `departamento`, `localidad_censal` y/o `localidad` para obtener resultados más precisos. 18 | 19 | ## Campos de respuesta 20 | 21 | Al normalizar una dirección, la API devuelve varios campos de datos. Para entender el significado de cada uno, es conveniente utilizar un ejemplo de uso: 22 | 23 | `GET` [https://apis.datos.gob.ar/georef/api/direcciones?direccion=Av. Santa Fe nro 260 2ndo C, entre Santa Rosa y Colón&departamento=capital&provincia=cordoba](https://apis.datos.gob.ar/georef/api/direcciones?direccion=Av.%20Santa%20Fe%20nro%20260%202ndo%20C,%20entre%20Santa%20Rosa%20y%20Col%C3%B3n&departamento=capital&provincia=cordoba) 24 | ```json 25 | { 26 | "cantidad": 1, 27 | "direcciones": [ 28 | { 29 | "altura": { 30 | "unidad": "nro", 31 | "valor": 260 32 | }, 33 | "calle": { 34 | "categoria": "AV", 35 | "id": "1401401002460", 36 | "nombre": "AV SANTA FE" 37 | }, 38 | "calle_cruce_1": { 39 | "categoria": "CALLE", 40 | "id": "1401401038100", 41 | "nombre": "SANTA ROSA" 42 | }, 43 | "calle_cruce_2": { 44 | "categoria": "AV", 45 | "id": "1401401002060", 46 | "nombre": "AV COLON" 47 | }, 48 | "departamento": { 49 | "id": "14014", 50 | "nombre": "Capital" 51 | }, 52 | "localidad_censal": { 53 | "id": "14014010", 54 | "nombre": "Córdoba" 55 | }, 56 | "nomenclatura": "AV SANTA FE 260 (ENTRE SANTA ROSA Y AV COLON), Capital, Córdoba", 57 | "piso": null, 58 | "provincia": { 59 | "id": "14", 60 | "nombre": "Córdoba" 61 | }, 62 | "ubicacion": { 63 | "lat": -31.4080674840673, 64 | "lon": -64.20062417513701 65 | } 66 | } 67 | ], 68 | "inicio": 0, 69 | "total": 1, 70 | "parametros": { ... } 71 | } 72 | ``` 73 | 74 | Como se puede observar, campos de respuesta estándar son: 75 | 76 | - `altura` 77 | - `unidad`: Unidad de la altura, o prefijo del valor numérico de la misma. 78 | - `valor`: Valor numérico de la altura, o `null` si la altura ingresada no fue numérica. 79 | - `calle`: Propiedades de la primera calle presente en la dirección. 80 | - `nombre`: Nombre normalizado de la **calle 1**. 81 | - `id`: ID de la **calle 1**. 82 | - `categoria`: Tipo de la **calle 1**. 83 | - `calle_cruce_1`: Propiedades de la segunda calle presente en la dirección *(valores opcionales)*. 84 | - `nombre`: Nombre normalizado de la **calle 2**. 85 | - `id`: ID de la **calle 2**. 86 | - `categoria`: Tipo de la **calle 2**. 87 | - `calle_cruce_1`: Propiedades de la tercera calle presente en la dirección *(valores opcionales)*. 88 | - `nombre`: Nombre normalizado de la **calle 3**. 89 | - `id`: ID de la **calle 3**. 90 | - `categoria`: Tipo de la **calle 3**. 91 | - `departamento`: Departamento de la **calle 1**. 92 | - `localidad_censal`: Localidad censal de la **calle 1**. 93 | - `provincia`: Provincia de la **calle 1**. 94 | - `piso`: Piso extraído de la dirección. 95 | - `nomenclatura`: Versión normalizada de la dirección. 96 | - `ubicacion`: Resultados de la georreferenciación de la dirección (`lon` y `lat`). Cuando los valores están presentes, representan una **aproximación** de la ubicación de la dirección. Cuando no están presentes, se debe a que los datos indexados en la API no fueron suficientes para obtener un resultado estimativo. La efectividad de la georreferenciación varía de acuerdo a cada región del país. 97 | 98 | ## Normalización de direcciones por lotes 99 | 100 | Para normalizar grandes cantidades de direcciones, se recomienda utilizar los recursos de [consultas por lotes](bulk.md). 101 | -------------------------------------------------------------------------------- /docs/src/applications.md: -------------------------------------------------------------------------------- 1 | # Aplicaciones 2 | 3 | Lista de proyectos y aplicaciones que utilizan, extienden o documentan la API del Servicio de Normalización de Datos Geográficos. 4 | 5 | ## De terceros 6 | 7 | * [pdelboca/georefar](https://github.com/pdelboca/georefar): Interface en R para usar la API por [@pdelboca](https://twitter.com/pdelboca). 8 | * [juancarlospaco/nim-georefar](https://github.com/juancarlospaco/nim-georefar): Inferface en Nim para usar la API por [juancarlospaco (GitHub)](https://github.com/juancarlospaco). 9 | 10 | ## Del equipo de Datos 11 | 12 | * [datosgobar/taller-georef-pyconar-2018](https://github.com/datosgobar/taller-georef-pyconar-2018): Taller realizado durante PyConAr 2018 (Buenos Aires). Contiene ejemplos de uso de la API con Pandas y Jupyter Notebook. 13 | 14 | ## Contacto 15 | 16 | ¿Desarrollaste una librería o tenés un proyecto que usa la API? Te [invitamos a contarnos](https://twitter.com/datosgobar) así la agregamos a esta lista. 17 | 18 | Para todo lo demás, podés mandarnos tu comentario o consulta a [datosargentina@jefatura.gob.ar](mailto:datosargentina@jefatura.gob.ar). 19 | -------------------------------------------------------------------------------- /docs/src/assets/calles1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/src/assets/calles1.png -------------------------------------------------------------------------------- /docs/src/assets/departamentos1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/src/assets/departamentos1.png -------------------------------------------------------------------------------- /docs/src/assets/google_drive_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/src/assets/google_drive_1.png -------------------------------------------------------------------------------- /docs/src/assets/google_drive_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/src/assets/google_drive_2.png -------------------------------------------------------------------------------- /docs/src/assets/google_drive_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/src/assets/google_drive_3.png -------------------------------------------------------------------------------- /docs/src/assets/google_drive_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/src/assets/google_drive_4.png -------------------------------------------------------------------------------- /docs/src/assets/google_drive_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/src/assets/google_drive_5.png -------------------------------------------------------------------------------- /docs/src/assets/google_drive_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/src/assets/google_drive_6.png -------------------------------------------------------------------------------- /docs/src/assets/google_drive_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/src/assets/google_drive_7.png -------------------------------------------------------------------------------- /docs/src/assets/google_drive_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/src/assets/google_drive_8.png -------------------------------------------------------------------------------- /docs/src/assets/google_drive_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/docs/src/assets/google_drive_9.png -------------------------------------------------------------------------------- /docs/src/communes.md: -------------------------------------------------------------------------------- 1 | # Comunas de CABA 2 | Las comunas de la Ciudad Autónoma de Buenos Aires (CABA) se encuentran disponibles bajo el recurso `/departamentos`. Los IDs y los nombres de las comunas están definidos por la [Resolución 55/2019 de INDEC](https://www.boletinoficial.gob.ar/detalleAviso/primera/203487/20190318), y son los siguientes: 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
IDNombre Oficial
02007Comuna 1
02014Comuna 2
02021Comuna 3
02028Comuna 4
02035Comuna 5
02042Comuna 6
02049Comuna 7
02056Comuna 8
02063Comuna 9
02070Comuna 10
02077Comuna 11
02084Comuna 12
02091Comuna 13
02098Comuna 14
02105Comuna 15
22 | -------------------------------------------------------------------------------- /docs/src/css/extra.css: -------------------------------------------------------------------------------- 1 | table { 2 | text-align: center; 3 | margin: 0 auto; 4 | } 5 | 6 | tbody { 7 | display: inline; 8 | } 9 | -------------------------------------------------------------------------------- /docs/src/deploy.md: -------------------------------------------------------------------------------- 1 | # Entornos productivos 2 | 3 | Para instalar la API de Georef en un entorno productivo, es necesario completar todos los pasos detallados en la [guía de instalación y ejecución](georef-api-development.md). Luego, seguir los siguientes pasos adicionales: 4 | 5 | ## 1. Configurar servicio para systemd 6 | Copiar el archivo [`config/georef-ar-api.service`](https://github.com/datosgobar/georef-ar-api/blob/master/config/georef-ar-api.service) a `/etc/systemd/system/` y configurarlo. **Notar los campos marcados entre '`<`' y '`>`'**, que deben ser reemplazados por los valores apropiados. 7 | 8 | Luego, activar y arrancar el nuevo servicio: 9 | ```bash 10 | $ sudo systemctl daemon-reload 11 | $ sudo systemctl enable georef-ar-api 12 | $ sudo systemctl start georef-ar-api 13 | ``` 14 | 15 | Una vez ejecutados los comandos, comprobar que la API esté funcionando: 16 | ```bash 17 | $ curl localhost:5000/api/provincias 18 | ``` 19 | 20 | El servicio instalado asegura que la API sea ejecutada al reinicar el sistema operativo, y se encarga de automáticamente almacenar todos los *logs* generados por la API. Los mismos pueden ser consultados utilizando: 21 | ```bash 22 | $ sudo journalctl -u georef-ar-api 23 | ``` 24 | 25 | Ver `man journalctl` para más detalles de uso. 26 | 27 | ## 2. Configurar Nginx 28 | Resulta conveniente configurar un servidor Nginx que actúe como receptor de todas las consultas destinadas al servidor de la API. El servidor Nginx permite controlar tamaños máximos de consultas y respuestas, establecer *caches*, y muchas más utilidades. Asumiendo que se instaló Nginx utilizando el administrador de paquetes (`apt`), seguir los siguientes pasos: 29 | 30 | ### 2.1 Crear archivo de configuración 31 | Primero, crear `/etc/nginx/sites-available/georef-ar-api.nginx` tomando como base la configuración del archivo [`georef-ar-api.nginx`](https://github.com/datosgobar/georef-ar-api/blob/master/config/georef-ar-api.nginx). 32 | 33 | ### 2.2 (Opcional) Configurar *cache* 34 | Si se desea activar el uso del *cache* de Nginx, descomentar las líneas contentiendo las directivas `proxy_cache` y `proxy_cache_valid` del archivo `georef-ar-api.nginx` creado. Luego, activar el *cache* `georef` agregando la siguiente línea al archivo de configuración `nginx.conf` (sección `http`): 35 | 36 | ```nginx 37 | proxy_cache_path /data/nginx/cache levels=1:2 inactive=120m keys_zone=georef:10m use_temp_path=off; 38 | ``` 39 | 40 | Finalmente, crear el directorio `/data/nginx/cache`. 41 | 42 | ### 2.3 Activar y validar la configuración 43 | Generar un link simbólico a la configuración de Georef: 44 | ```bash 45 | $ sudo ln -s /etc/nginx/sites-available/georef-ar-api.nginx /etc/nginx/sites-enabled/georef-ar-api.nginx 46 | ``` 47 | 48 | Validar la configuración: 49 | ```bash 50 | $ sudo nginx -T 51 | ``` 52 | 53 | Finalmente, reiniciar Nginx: 54 | ```bash 55 | $ sudo systemctl restart nginx.service 56 | ``` 57 | 58 | Una vez ejecutados los comandos, comprobar nuevamente que la API esté funcionando, en el puerto 80 estándar: 59 | ```bash 60 | $ curl localhost/api/provincias 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/src/download.md: -------------------------------------------------------------------------------- 1 | # Descarga de base completa 2 | 3 | Si se desea operar con la enteridad de los datos utilizados por la API, es recomendable descargarse la base completa de datos. 4 | 5 | Cada recurso (sin contar `/ubicacion`) de la API cuenta con variantes de descarga completa. Para acceder a las variantes, agregar `.json`, `.csv`, o `.geojson` al final de la ruta del recurso. Por ejemplo: 6 | 7 | - [https://apis.datos.gob.ar/georef/api/calles.json](https://apis.datos.gob.ar/georef/api/calles.json) 8 | - [https://apis.datos.gob.ar/georef/api/provincias.csv](https://apis.datos.gob.ar/georef/api/provincias.csv) 9 | - [https://apis.datos.gob.ar/georef/api/localidades.geojson](https://apis.datos.gob.ar/georef/api/provincias.geojson) 10 | -------------------------------------------------------------------------------- /docs/src/etl.md: -------------------------------------------------------------------------------- 1 | # ETL del Servicio de Normalización de Datos Geográficos de Argentina 2 | 3 | El ETL Georef es el proyecto encargado de generar los activos de datos indexados por la API. El producto final del ETL es una serie de archivos que contienen información sobre varias entidades geográficas de la República Argentina. En el [portal de Datos Abiertos](https://datos.gob.ar/dataset/modernizacion-servicio-normalizacion-datos-geograficos) se encuentra el listado de los archivos, incluyendo enlaces de descarga. 4 | 5 | Las siguientes secciones de la documentación detallan la estructura de los archivos mencionados, y los pasos a seguir para instalar y ejecutar el ETL en un entorno propio. 6 | 7 | El código que compone el ETL puede encontrarse en [GitHub](https://github.com/datosgobar/georef-ar-etl). 8 | -------------------------------------------------------------------------------- /docs/src/geom-operations.md: -------------------------------------------------------------------------------- 1 | # Operaciones con Geometrías 2 | La API permite a los usuarios operar con las geometrías de distintas entidades geográficas. A continuación, se detallan los recursos y parámetros que permiten a los usuarios realizar estas operaciones. 3 | 4 | ## Parámetro `interseccion` 5 | Los recursos `/provincias`, `/departamentos`, `/municipios` y `/calles` cuentan con el parámetro `interseccion`. El parámetro permite buscar entidades utilizando intersección de geometrías como filtro. El parámetro debe tomar valores con el siguiente formato: 6 | 7 | interseccion=:[::...] 8 | 9 | Al aplicar el filtro `interseccion`, se buscan entidades que compartan área con cualquiera de las entidades listadas en la lista de IDs. Entonces, utilizar (por ejemplo) `/municipios?interseccion=departamento:18105` buscaría todos los municipios que interseccionen con el departamento con ID 18105, mientras que utilizar `/departamentos?interseccion=municipio:620133:540378` buscaría todos los departamentos que interseccionen con el municipio con ID 620133 **o** el municipio con ID 540378. 10 | 11 | De la misma forma, utilizar `/calles?interseccion=municipio:620133` buscaría todas las calles que estén contenidas en el municipio con ID 620133. También es posible buscar municipios a partir de una calle: `/municipios?interseccion=calle:0638503000235` buscaría el municipio que contiene a la calle con ID 0638503000235. 12 | 13 | !!! warning "IDs inválidos" 14 | Todos los IDs listados que no correspondan a una entidad geográfica existente serán ignorados. 15 | 16 | Ejemplo completo de llamado a la API: 17 | 18 | `GET` [`https://apis.datos.gob.ar/georef/api/municipios?interseccion=departamento:18105`](https://apis.datos.gob.ar/georef/api/municipios?interseccion=departamento:18105) 19 | ```json 20 | { 21 | "municipios": [ 22 | { 23 | "centroide": { 24 | "lat": -28.508559, 25 | "lon": -58.031593 26 | }, 27 | "id": "180042", 28 | "nombre": "Concepción", 29 | "provincia": { 30 | "id": "18", 31 | "nombre": "Corrientes" 32 | } 33 | }, 34 | { ... } // 9 resultados omitidos 35 | ], 36 | "cantidad": 10, 37 | "total": 13, 38 | "inicio": 0, 39 | "parametros": { ... } 40 | } 41 | ``` 42 | 43 | Aunque el recurso `/provincias` acepta el parámetro `interseccion`, se recomienda utilizar filtros por IDs y no por geometrías al momento de buscar provincias, ya que el filtrado por ID es más performante. Por ejemplo, la consulta `/provincias?interseccion=departamento:18105` es equivalente a la consulta `/provincias?id=18`, ya que los primeros dos dígitos de los códigos de los departamentos siempre corresponden a su provincia. 44 | 45 | ## Recurso `/ubicacion` 46 | En la [sección de inicio](/), se dió un ejemplo de uso del recurso `/ubicacion` para enriquecer datos existentes. El recurso utiliza las geometrías de las entidades geográficas para determinar cuáles contienen al punto especificado por el usuario a través de los parámetros `lat` y `lon`. Las entidades devueltas son las siguientes: 47 | 48 | - Provincia 49 | - Departamento 50 | - Municipio *(opcional)* 51 | 52 | Dependiendo del punto elegido, es posible no obtener un municipio como parte de la respuesta de la API. Como ejemplo, se muestran dos llamados distintos al recurso `/ubicacion`. 53 | 54 | Con municipio: 55 | 56 | `GET` [`https://apis.datos.gob.ar/georef/api/ubicacion?lat=-27.2741&lon=-66.7529`](https://apis.datos.gob.ar/georef/api/ubicacion?lat=-27.2741&lon=-66.7529) 57 | ```json 58 | { 59 | "ubicacion": { 60 | "departamento": { 61 | "id": "10035", 62 | "nombre": "Belén" 63 | }, 64 | "lat": -27.2741, 65 | "lon": -66.7529, 66 | "municipio": { 67 | "id": "100077", 68 | "nombre": "Hualfín" 69 | }, 70 | "provincia": { 71 | "id": "10", 72 | "nombre": "Catamarca" 73 | } 74 | }, 75 | "parametros": { ... } 76 | } 77 | ``` 78 | 79 | Sin municipio: 80 | 81 | `GET` [`https://apis.datos.gob.ar/georef/api/ubicacion?lat=-28.504&lon=-62.898`](https://apis.datos.gob.ar/georef/api/ubicacion?lat=-28.504&lon=-62.898) 82 | ```json 83 | { 84 | "ubicacion": { 85 | "departamento": { 86 | "id": "86028", 87 | "nombre": "Avellaneda" 88 | }, 89 | "lat": -28.504, 90 | "lon": -62.898, 91 | "municipio": { 92 | "id": null, 93 | "nombre": null 94 | }, 95 | "provincia": { 96 | "id": "86", 97 | "nombre": "Santiago del Estero" 98 | } 99 | }, 100 | "parametros": { ... } 101 | } 102 | ``` 103 | -------------------------------------------------------------------------------- /docs/src/index.md: -------------------------------------------------------------------------------- 1 | # API del Servicio de Normalización de Datos Geográficos de Argentina 2 | 3 | **Versión**: 0.5.3 4 | 5 | La API del Servicio de Normalización de Datos Geográficos, permite normalizar y codificar los nombres de unidades territoriales de la Argentina (provincias, departamentos, municipios y localidades) y de sus calles, así como ubicar coordenadas dentro de ellas. 6 | 7 | En la [Guía para la identificación y uso de entidades interoperables](https://datosgobar.github.io/paquete-apertura-datos/guia-interoperables/) se explica cómo funcionan las [unidades territoriales internas de la Argentina](https://datosgobar.github.io/paquete-apertura-datos/guia-interoperables/#divisiones-o-unidades-territoriales-internas) y la relación entre ellas. 8 | 9 | ## Normalizar 10 | 11 | Las unidades territoriales tienen nombres y códigos oficiales. Cuando no se usan, los datos son difíciles de cruzar entre sí y hay que normalizarlos antes. 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
provincia
Santiago del Estero
Stgo. del Estero
S. del Estero
Sgo. del Estero
20 | 21 | `GET` [`https://apis.datos.gob.ar/georef/api/provincias?nombre=Sgo. del Estero`](https://apis.datos.gob.ar/georef/api/provincias?nombre=Sgo.%20del%20Estero) 22 | 23 | ```json 24 | { 25 | "provincias": [ 26 | { 27 | "nombre": "Santiago del Estero", 28 | "id": "86", 29 | "centroide": { 30 | "lat": -27.782412, 31 | "lon": -63.252387 32 | } 33 | } 34 | ], 35 | "cantidad": 1, 36 | "total": 1, 37 | "inicio": 0, 38 | "parametros": { ... } 39 | } 40 | ``` 41 | 42 | ## Enriquecer 43 | 44 | Cuando un conjunto de datos tiene puntos de coordenadas dentro de Argentina, puede cruzarse con muchos datos más, relacionados a las unidades territoriales que lo contienen. Para esto hay que agregarlas a los datos originales. 45 | 46 | 47 | 48 | 49 | 50 |
latlon
-27.2741-66.7529
-34.603633-58.3837587
51 | 52 | `GET` [`https://apis.datos.gob.ar/georef/api/ubicacion?lat=-27.2741&lon=-66.7529`](https://apis.datos.gob.ar/georef/api/ubicacion?lat=-27.2741&lon=-66.7529) 53 | ```json 54 | { 55 | "ubicacion": { 56 | "departamento": { 57 | "id": "10035", 58 | "nombre": "Belén" 59 | }, 60 | "lat": -27.2741, 61 | "lon": -66.7529, 62 | "municipio": { 63 | "id": "100077", 64 | "nombre": "Hualfín" 65 | }, 66 | "provincia": { 67 | "id": "10", 68 | "nombre": "Catamarca" 69 | } 70 | }, 71 | "parametros": { ... } 72 | } 73 | ``` 74 | 75 | ## Referencia 76 | 77 | Finalmente, se puede utilizar la API como punto de referencia al momento de crear datos que estén vinculados a datos geográficos. Por ejemplo, si se cuenta con un formulario en el que se debe mostrar a un usuario un listado de provincias, y luego un listado de municipios a partir de la provincia seleccionada, se podrían ejecutar las siguientes consultas: 78 | 79 | Listar las provincias de la República Argentina: 80 | 81 | `GET` [`https://apis.datos.gob.ar/georef/api/provincias?campos=id,nombre`](https://apis.datos.gob.ar/georef/api/provincias?campos=id,nombre) 82 | ```json 83 | { 84 | "provincias": [ 85 | { 86 | "nombre": "Chaco", 87 | "id": "22" 88 | }, 89 | { ... } // 23 resultados omitidos 90 | ], 91 | "cantidad": 24, 92 | "total": 24, 93 | "inicio": 0, 94 | "parametros": { ... } 95 | } 96 | ``` 97 | 98 | Asumiendo que el usuario selecciona **Chaco** (ID: **22**), se ejecutaría la siguiente consulta para obtener el listado de municipios: 99 | 100 | `GET` [`https://apis.datos.gob.ar/georef/api/municipios?provincia=22&campos=id,nombre&max=100`](https://apis.datos.gob.ar/georef/api/municipios?provincia=22&campos=id,nombre&max=100) 101 | ```json 102 | { 103 | "municipios": [ 104 | { 105 | "nombre": "Makallé", 106 | "id": "220161" 107 | }, 108 | { ... } // 67 resultados omitidos 109 | ], 110 | "cantidad": 68, 111 | "total": 68, 112 | "inicio": 0, 113 | "parametros": { ... } 114 | } 115 | ``` 116 | 117 | Notar que al ser datos que no son modificados regularmente, es posible retener copias de los mismos para ser reutilizados en el futuro. 118 | 119 | --- 120 | 121 | *Si sos usuario de la API de Normalización de Datos Geográficos y querés estar al tanto de los cambios y novedades, [inscribite en la base de contactos de Datos Argentina](bit.ly/contacto-datos-argentina) y elegí sobre qué temas querés que te escribamos.* 122 | 123 | *¿Tenés algo que contarnos? Nos encantaría que nos cuentes por [Twitter](https://twitter.com/datosgobar) o por [mail](mailto:datosargentina@jefatura.gob.ar) qué mejoró usar la API en tu trabajo y qué le falta para mejorar aún más.* 124 | -------------------------------------------------------------------------------- /docs/src/jwt-token.md: -------------------------------------------------------------------------------- 1 | # Tokens JWT 2 | 3 | Si pertenecés a un organismo de la Administración Pública Nacional y querés incrementar la cuota de uso de la API de Georef, podés pedir un token y autenticarte utilizando [JWT](https://jwt.io/). 4 | 5 | Para generar un token JWT, se requieren dos elementos: una *key* y un *secret* generados para el uso con la API. 6 | 7 | Una vez obtenidos ambos elementos, se puede generar un token JWT utilizando, por ejemplo, Python o Node.js. A continuación, se muestran ejemplos utilizando los siguientes valores demostrativos: 8 | 9 | - `key = YXNkc2Rhc2RmYXNkZmFzZmRhc2RmYXNk` 10 | - `secret = dnVvODY4Yzc2bzhzNzZqOG83czY4b2Nq` 11 | 12 | El algoritmo de autentificación de mensajes con *hash* utilizado es HMAC-SHA256 (`HS256`). 13 | 14 | ## Python 15 | 16 | Utilizando la librería [`pyjwt`](https://github.com/jpadilla/pyjwt): 17 | 18 | ```bash 19 | $ pip install pyjwt 20 | $ python 21 | ``` 22 | ```python 23 | >>> import jwt 24 | >>> key = 'YXNkc2Rhc2RmYXNkZmFzZmRhc2RmYXNk' 25 | >>> message = { 'iss': key } 26 | >>> secret = 'dnVvODY4Yzc2bzhzNzZqOG83czY4b2Nq' 27 | >>> token_bytes = jwt.encode(message, secret, algorithm='HS256') 28 | >>> token = token_bytes.decode() 29 | >>> token 30 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJZWE5rYzJSaGMyUm1ZWE5rWm1GelptUmhjMlJtWVhOayJ9.P4leoe9q_H3lmIlnpZuVFSt7ORgLhLfQ3JN_3FMexSo' 31 | ``` 32 | 33 | Finalmente, para consumir la API de Georef, adjuntar el token generado en las cabeceras HTTP. A continuación, se muestra un ejemplo utilizando la librería [`requests`](http://docs.python-requests.org/en/master/): 34 | 35 | ```python 36 | >>> import requests 37 | >>> headers = { 'Authorization': 'Bearer {}'.format(token) } 38 | >>> resp = requests.get('https://apis.datos.gob.ar/georef/api/provincias', headers=headers) 39 | >>> resp.json() 40 | { 41 | 'provincias': [ 42 | { ... } 43 | ] 44 | } 45 | ``` 46 | 47 | ## Node.js 48 | 49 | Utilizando la librería [`jswonwebtoken`](https://github.com/auth0/node-jsonwebtoken): 50 | 51 | ```bash 52 | $ npm install jsonwebtoken 53 | $ node 54 | ``` 55 | ```javascript 56 | > var jwt = require('jsonwebtoken') 57 | > var payload = { 'iss': 'YXNkc2Rhc2RmYXNkZmFzZmRhc2RmYXNk' } 58 | > var secret = 'dnVvODY4Yzc2bzhzNzZqOG83czY4b2Nq' 59 | > var token = jwt.sign(payload, secret, { 'noTimestamp': true }) 60 | > token 61 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJZWE5rYzJSaGMyUm1ZWE5rWm1GelptUmhjMlJtWVhOayJ9.P4leoe9q_H3lmIlnpZuVFSt7ORgLhLfQ3JN_3FMexSo' 62 | ``` 63 | 64 | Finalmente, para consumir la API de Georef, adjuntar el token generado en las cabeceras HTTP: 65 | 66 | ```javascript 67 | > var http = require('http') 68 | > http.get({ 69 | 'hostname': 'apis.datos.gob.ar', 70 | 'path': '/georef/api/provincias', 71 | 'headers': { 72 | 'authorization': 'Bearer ' + token 73 | } 74 | }, function(response) { 75 | ... 76 | }) 77 | ``` 78 | -------------------------------------------------------------------------------- /docs/src/localities.md: -------------------------------------------------------------------------------- 1 | # Localidades y asentamientos 2 | 3 | Entre los distintos tipos de entidades geográficas que maneja la API, se encuentran las **localidades censales**, las **localidades** y los **asentamientos**. A continuación se explica en detalle la definición de cada una de estas entidades. 4 | 5 | ## Localidades censales 6 | El listado de **localidades censales** proviene de [INDEC](https://www.indec.gov.ar/codgeo.asp). Las localidades censales son unidades geoestadísticas elaboradas por INDEC para los censos nacionales. 7 | 8 | El recurso correspondiente de la API es [`/localidades-censales`](https://apis.datos.gob.ar/georef/api/localidades-censales). 9 | 10 | ## Localidades 11 | Las **localidades** (a no ser confundidas con las localidades censales) provienen de [BAHRA](http://www.bahra.gob.ar/). La base BAHRA identifica localidades, parajes y sitios edificados de la República Argentina con un nombre y un código único. 12 | 13 | El listado de localidades se contruye a partir de un **subconjunto de BAHRA**. Nosotros consideramos que **este subconjunto es el que mejor representa el concepto general de "localidad", el cual no tiene definición formal**. 14 | 15 | Notar que todas las localidades pertenecen a una localidad censal. 16 | 17 | El recurso correspondiente de la API es [`/localidades`](https://apis.datos.gob.ar/georef/api/localidades). 18 | 19 | ## Asentamientos 20 | Los **asentamientos** también provienen de BAHRA. Sin embargo, el listado de asentamientos se contruye a partir de la **enteridad de BAHRA**. Esto implica que todas las entidades del listado de localidades están incluidas en el listado de asentamientos. El listado de asentamientos, sin embargo, contiene entidades que en general un usuario no consideraría ser una "localidad". 21 | 22 | Notar que los asentamientos **opcionalmente** pueden pertenecer a una localidad censal. 23 | 24 | El recurso correspondiente de la API es [`/asentamientos`](https://apis.datos.gob.ar/georef/api/asentamientos). 25 | -------------------------------------------------------------------------------- /docs/src/python3.6.md: -------------------------------------------------------------------------------- 1 | # Instalación de Python 3.6 2 | 3 | Para instalar Python 3.6 en entornos GNU/Linux, se puede utilizar la herramienta `pyenv` [disponible en GitHub](https://github.com/pyenv/pyenv). `pyenv` permite al usuario instalar cualquier versión de Python existente, e incluso tener varias versiones instaladas simultáneamente. 4 | 5 | A continuación, se detallan los pasos necesarios para instalar Python 3.6. Los mismos fueron creados utilizando Ubuntu 16.04. 6 | 7 | ## 1. Descargar `pyenv` 8 | Clonar el repositorio de `pyenv` en el directorio `~/.pyenv`: 9 | ```bash 10 | $ git clone https://github.com/pyenv/pyenv.git ~/.pyenv 11 | ``` 12 | 13 | ## 2. Agregar configuración de `pyenv` a `~/.bashrc` 14 | ```bash 15 | $ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc 16 | $ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc 17 | $ echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n eval "$(pyenv init -)"\nfi' >> ~/.bashrc 18 | ``` 19 | 20 | ## 3. Activar la nueva configuración 21 | ```bash 22 | $ source ~/.bashrc 23 | ``` 24 | 25 | ## 4. Instalar dependencias para compilar Python 26 | ```bash 27 | $ sudo apt install make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev libffi-dev liblzma-dev 28 | ``` 29 | 30 | ## 5. Descargar, compilar e instalar Python 3.6 31 | ```bash 32 | $ pyenv install 3.6.5 33 | ``` 34 | 35 | ## 6. Activar Python 3.6 36 | Una vez instalado Python 3.6, se debe activar su uso. `pyenv` permite establecer versiones de Python por directorio: de esta forma, es posible clonar el repositorio `georef-ar-api` en una ubicación, y activar el uso de Python 3.6 en la misma: 37 | ```bash 38 | $ git clone https://github.com/datosgobar/georef-ar-api.git 39 | $ cd georef-ar-api 40 | $ pyenv version 3.6.5 # activar el uso de Python 3.6 41 | $ python --version # el comando 'python' ahora utiliza Python 3.6, en este directorio 42 | Python 3.6.5 43 | $ pip --version # también se instala 'pip' automáticamente 44 | pip 9.0.1 (python 3.6.5) 45 | ``` 46 | Notar que `pyenv` crea un archivo llamado `.python-version`, donde se especifica la versión de Python que debería ser utilizada en el directorio. 47 | -------------------------------------------------------------------------------- /docs/src/quick-start.md: -------------------------------------------------------------------------------- 1 | # Ejemplos de uso 2 | 3 | ## Ejemplos rápidos 4 | A continuación, se muestran algunos ejemplos de uso de la API, utilizando los recursos `GET`: 5 | 6 | ### Búsqueda de provincias 7 | `GET` [`https://apis.datos.gob.ar/georef/api/provincias?nombre=cordoba`](https://apis.datos.gob.ar/georef/api/provincias?nombre=cordoba) 8 | ``` 9 | { 10 | "provincias": [ 11 | { 12 | "id": "14", 13 | "centroide": { 14 | "lat": -32.142933, 15 | "lon": -63.801753 16 | }, 17 | "nombre": "CÓRDOBA" 18 | } 19 | ], 20 | "cantidad": 1, 21 | "total": 1, 22 | "inicio": 0, 23 | "parametros": { ... } 24 | } 25 | ``` 26 | 27 | ### Búsqueda de departamentos 28 | `GET` [`https://apis.datos.gob.ar/georef/api/departamentos?provincia=jujuy&max=16`](https://apis.datos.gob.ar/georef/api/departamentos?provincia=jujuy&max=16) 29 | ``` 30 | { 31 | "departamentos": [ 32 | { 33 | "id": "38042", 34 | "centroide": { 35 | "lat": -24.194923, 36 | "lon": -65.12645 37 | }, 38 | "nombre": "PALPALÁ", 39 | "provincia": { 40 | "id": "38", 41 | "nombre": "JUJUY" 42 | } 43 | }, 44 | { ... } // 15 departamentos omitidos 45 | ], 46 | "cantidad": 16, 47 | "total": 16, 48 | "inicio": 0, 49 | "parametros": { ... } 50 | } 51 | ``` 52 | 53 | ### Búsqueda de municipios 54 | `GET` [`https://apis.datos.gob.ar/georef/api/municipios?provincia=tucuman&aplanar`](https://apis.datos.gob.ar/georef/api/municipios?provincia=tucuman&aplanar) 55 | ``` 56 | { 57 | "municipios": [ 58 | { 59 | "centroide_lat": -27.816619, 60 | "centroide_lon": -65.199594, 61 | "id": "908210", 62 | "nombre": "Taco Ralo", 63 | "provincia_id": "90", 64 | "provincia_nombre": "Tucumán" 65 | }, 66 | { ... } // 9 municipios omitidos 67 | ], 68 | "cantidad": 10, 69 | "total": 112, 70 | "inicio": 0, 71 | "parametros": { ... } 72 | } 73 | ``` 74 | 75 | ### Búsqueda de localidades 76 | `GET` [`https://apis.datos.gob.ar/georef/api/localidades?provincia=chubut&campos=nombre`](https://apis.datos.gob.ar/georef/api/localidades?provincia=chubut&campos=nombre) 77 | ``` 78 | { 79 | "localidades": [ 80 | { 81 | "id": "26007030000", 82 | "nombre": "PUERTO PIRAMIDE" 83 | }, 84 | { ... } // 9 resultados omitidos 85 | ], 86 | "cantidad": 10, 87 | "total": 90, 88 | "inicio": 0, 89 | "parametros": { ... } 90 | } 91 | ``` 92 | 93 | ### Normalización de direcciones 94 | `GET` [`https://apis.datos.gob.ar/georef/api/direcciones?departamento=merlo&direccion=Florida al 2950`](https://apis.datos.gob.ar/georef/api/direcciones?departamento=merlo&direccion=Florida%20al%202950) 95 | ``` 96 | { 97 | "direcciones": [ 98 | { 99 | "altura": { 100 | "unidad": null, 101 | "valor": 2950 102 | }, 103 | "calle": { 104 | "categoria": "CALLE", 105 | "id": "0653901002985", 106 | "nombre": "FLORIDA" 107 | }, 108 | "calle_cruce_1": { 109 | "categoria": null, 110 | "id": null, 111 | "nombre": null 112 | }, 113 | "calle_cruce_2": { 114 | "categoria": null, 115 | "id": null, 116 | "nombre": null 117 | }, 118 | "departamento": { 119 | "id": "06539", 120 | "nombre": "Merlo" 121 | }, 122 | "localidad_censal": { 123 | "id": "06539010", 124 | "nombre": "Merlo" 125 | }, 126 | "nomenclatura": "FLORIDA 2950, Merlo, Buenos Aires", 127 | "piso": null, 128 | "provincia": { 129 | "id": "06", 130 | "nombre": "Buenos Aires" 131 | }, 132 | "ubicacion": { 133 | "lat": -34.71409079053823, 134 | "lon": -58.73469942899891 135 | } 136 | } 137 | ], 138 | "cantidad": 1, 139 | "total": 1, 140 | "inicio": 0, 141 | "parametros": { ... } 142 | } 143 | ``` 144 | 145 | ### Listado de calles 146 | `GET` [`https://apis.datos.gob.ar/georef/api/calles?departamento=rio chico&categoria=avenida`](https://apis.datos.gob.ar/georef/api/calles?departamento=rio chico&categoria=avenida) 147 | ``` 148 | { 149 | "calles": [ 150 | { 151 | "altura": { 152 | "fin": { 153 | "derecha": 0, 154 | "izquierda": 0 155 | }, 156 | "inicio": { 157 | "derecha": 0, 158 | "izquierda": 0 159 | } 160 | }, 161 | "departamento": { 162 | "id": "90077", 163 | "nombre": "Río Chico" 164 | }, 165 | "localidad_censal": { 166 | "id": "90077010", 167 | "nombre": "Aguilares" 168 | }, 169 | "id": "9007701000050", 170 | "nombre": "AV GRL SAVIO", 171 | "nomenclatura": "AV GRL SAVIO, Río Chico, Tucumán", 172 | "provincia": { 173 | "id": "90", 174 | "nombre": "Tucumán" 175 | }, 176 | "categoria": "AV" 177 | }, 178 | { ... } // 2 resultados omitidos 179 | ], 180 | "cantidad": 3, 181 | "total": 3, 182 | "inicio": 0, 183 | "parametros": { ... } 184 | } 185 | ``` 186 | -------------------------------------------------------------------------------- /docs/src/shapefiles.md: -------------------------------------------------------------------------------- 1 | # Descarga de Geometrías 2 | 3 | 4 | La API permite la descarga de geometrías a través del formato [ESRI Shapefile](https://es.wikipedia.org/wiki/Shapefile). Para utilizar el formato, se debe agregar `formato=shp` a la lista de parámetros especificados en la URL. El formato Shapefile está disponible en la versión GET de todos los recursos, con las excepción de `/direcciones` y `/ubicacion`. 5 | 6 | Cuando se especifica `formato=shp`, la respuesta de la API es un archivo ZIP que contiene cuatro archivos estándar: `.shp`, `.shx`, `.dbf` y `.prj`. El archivo luego puede ser abierto con programas como [QGIS](https://www.qgis.org/en/site/). El sistema de coordenadas de las geometrías descargadas es WGS84 (EPSG 4326). 7 | 8 | Por ejemplo, si se desea obtener todas las calles del municipio Alta García (ID 141372), se puede utilizar la siguiente consulta: 9 | 10 | `GET` [https://apis.datos.gob.ar/georef/api/calles?interseccion=municipio:141372&formato=shp&max=1000](https://apis.datos.gob.ar/georef/api/calles?interseccion=municipio:141372&formato=shp&max=1000) 11 | 12 | Que descargaría los siguientes datos: 13 | 14 | ![](assets/calles1.png) 15 |
16 | 17 | Si se desean descargar todos los departamentos de la provicina de Chaco, se puede utilizar la siguiente consulta: 18 | 19 | `GET` [https://apis.datos.gob.ar/georef/api/departamentos?provincia=chaco&formato=shp&max=1000](https://apis.datos.gob.ar/georef/api/departamentos?provincia=chaco&formato=shp&max=1000) 20 | 21 | Que descargaría los siguientes datos: 22 | 23 | ![](assets/departamentos1.png) 24 | -------------------------------------------------------------------------------- /docs/src/spreadsheet-integration.md: -------------------------------------------------------------------------------- 1 | # Integración con planillas de cálculo 2 | 3 | ## Google Drive 4 | 5 | ### 1. Modificar la configuración regional 6 | 7 | La API genera archivos CSV usando “.” como separador decimal. Para que Google Spreadsheet lea correctamente el archivo debe elegirse “Estados Unidos” o cualquier otra región compatible (esto sólo afecta a la lectura de coordenadas). 8 | 9 | ![](assets/google_drive_1.png) 10 |

11 | ![](assets/google_drive_2.png) 12 |

13 | ![](assets/google_drive_3.png) 14 | 15 | ### 2. Importar listados de unidades territoriales 16 | 17 | Utilizamos la función `IMPORTDATA()` de Google Sheets y armamos la url de la entidad territorial que queremos importar. Por ejemplo "localidades de la provincia de Santa Fé": 18 | 19 | [`https://apis.datos.gob.ar/georef/api/localidades?formato=csv&max=1000&provincia=santa%20fe`](https://apis.datos.gob.ar/georef/api/localidades?formato=csv&max=1000&provincia=santa%20fe) 20 | 21 | ![](assets/google_drive_4.png) 22 |

23 | 24 | y obtendremos: 25 | 26 | ![](assets/google_drive_5.png) 27 |

28 | 29 | ### 3. Normalizar un listado de unidades territoriales 30 | 31 | Si tenemos un listado de provincias que queremos normalizar, como el siguiente: 32 | 33 | ![](assets/google_drive_6.png) 34 |

35 | 36 | Podemos armar urls individuales para normalizar los nombres y traer alguno de sus atributos. Imaginemos que queremos el ID y el nombre normalizado. 37 | 38 | Primero generamos la url para cada una de las provincias: 39 | 40 | ![](assets/google_drive_7.png) 41 |

42 | 43 | y luego necesitamos importar una nueva función en la hoja de cálculo. Para eso, desde el menú: 44 | 45 | 1. Herramientas → Editor de secuencia de comandos. 46 | 2. Borramos todo lo que hay en el editor y pegamos el siguiente [script](https://raw.githubusercontent.com/bradjasper/ImportJSON/master/ImportJSON.gs) de [Bradjasper](https://github.com/bradjasper). 47 | 3. Renombremos el script como ImportJson.gs y guardamos. 48 | 4. Ahora ya podemos usar la función `=importJSON()` en una celda. 49 | 50 | ![](assets/google_drive_8.png) 51 |

52 | 53 | `=ImportJSON(B2;”/provincias/id,/provincias/nombre”;”noInherit,noTruncate,noHeaders”)` 54 | 55 | y obtendremos: 56 | 57 | ![](assets/google_drive_9.png) 58 |

59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /docs/src/terms.md: -------------------------------------------------------------------------------- 1 | # Condiciones de uso 2 | 3 | ## Legales 4 | 5 | Los datos del Servicio de Normalización de Datos Geográficos de Argentina se disponibilizan bajo la licencia [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/deed.es) y pueden ser usados para cualquier fin, incluyendo fines comerciales. 6 | 7 | ## Técnicas 8 | 9 | La API del servicio de normalización se encuentra en estado *alpha* de desarrollo y las cuotas de uso abierto sin autenticación no están definidas. 10 | 11 | A medida que el servicio madure en su desarrollo y en el uso que se le da, se establecerán cuotas explícitas de uso sin autenticación por IP. 12 | 13 | Los organismos de la APN que requieran cuotas de uso más elevadas que las del servicio abierto, pueden solicitar un token a [datosargentina@jefatura.gob.ar](mailto:datosargentina@jefatura.gob.ar). 14 | 15 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: API del Servicio de Normalización de Datos Geográficos de Argentina 2 | site_url: http://apis.datos.gob.ar/georef/ 3 | site_description: Documentación de la API del Servicio de Normalización de Datos Geográficos de la República Argentina 4 | site_author: Datos Argentina 5 | 6 | repo_url: https://github.com/datosgobar/georef-ar-api 7 | edit_uri: "" 8 | 9 | nav: 10 | - Inicio: index.md 11 | - Usuarios: 12 | - Ejemplos de uso: quick-start.md 13 | - Consultas por lotes: bulk.md 14 | - Normalización de direcciones: addresses.md 15 | - Operaciones con geometrías: geom-operations.md 16 | - Descarga de geometrías: shapefiles.md 17 | - Descarga de base completa: download.md 18 | - Usar en planillas de cálculo: spreadsheet-integration.md 19 | - Usar en Python: python-usage.md 20 | - Autenticarse con JWT: jwt-token.md 21 | - Referencia API: https://datosgobar.github.io/georef-ar-api/open-api 22 | - Referencia metodológica: 23 | - Comunas de CABA: communes.md 24 | - Localidades y asentamientos: localities.md 25 | # - Gobiernos locales y municipios: municipalities.md 26 | - Condiciones de uso: terms.md 27 | - Aplicaciones: applications.md 28 | - Desarrolladores: 29 | - Instalación y Ejecución: georef-api-development.md 30 | - Entornos productivos: deploy.md 31 | - Historial de versiones: history.md 32 | - Instalación de Python 3.6: python3.6.md 33 | - ETL: 34 | - Inicio: etl.md 35 | - Datos: etl-data.md 36 | - Instalación y Ejecución: etl-install.md 37 | 38 | docs_dir: docs/src 39 | site_dir: docs/site 40 | 41 | google_analytics: ['UA-91435482-1', "https://datosgobar.github.io/georef-ar-api/"] 42 | 43 | theme: 44 | name: datosgobar_docs 45 | highlightjs: true 46 | hljs_languages: 47 | - yaml 48 | - django 49 | - python 50 | 51 | extra_css: 52 | - css/extra.css 53 | 54 | plugins: 55 | - search: 56 | lang: es 57 | 58 | markdown_extensions: 59 | - toc: 60 | permalink: True 61 | - attr_list 62 | - fenced_code 63 | - sane_lists 64 | - admonition 65 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | flake8==3.5.0 2 | pylint==2.3.1 3 | coverage==4.5.2 4 | gprof2dot==2017.9.19 5 | -------------------------------------------------------------------------------- /requirements-docs.txt: -------------------------------------------------------------------------------- 1 | mkdocs-datosgobar==1.0.15 2 | mkdocs-material==4.1.1 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | elasticsearch-dsl==7.0.0 2 | Flask==2.0.0 3 | georef-ar-address==0.0.9 4 | geojson==2.3.0 5 | gunicorn[gevent]==19.9.0 6 | requests==2.20.0 7 | shapely==1.6.4.post2 8 | pyshp==2.0.1 9 | tqdm==4.31.1 10 | Werkzeug==2.3.7 11 | -------------------------------------------------------------------------------- /service/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa: F401 2 | # pylint: disable=wrong-import-position 3 | """Módulo '__init__' de georef-ar-api 4 | 5 | Crea la aplicación Flask de la API de Georef. 6 | """ 7 | 8 | from flask import Flask 9 | 10 | app = Flask('georef', static_folder=None) 11 | app.config.from_envvar('GEOREF_CONFIG') 12 | 13 | with app.app_context(): 14 | # Crear parsers de parámetros utilizando configuración de Flask app 15 | import service.params 16 | # Crear rutas utilizando también configuración de Flask 17 | import service.routes 18 | 19 | from service import utils 20 | utils.patch_json_encoder(app) 21 | 22 | 23 | def georef_console(): 24 | """Inicia una consola interactiva de Python con algunos módulos de 25 | georef-ar-api precargados para realizar pruebas rápidas. 26 | 27 | """ 28 | import code 29 | with app.app_context(): 30 | console = code.InteractiveConsole(locals=locals()) 31 | console.push('import service') 32 | console.push('es = service.normalizer.get_elasticsearch()') 33 | console.interact() 34 | -------------------------------------------------------------------------------- /service/constants.py: -------------------------------------------------------------------------------- 1 | """Módulo 'constants' de georef-ar-api 2 | 3 | Contiene variables con valores constantes. 4 | """ 5 | 6 | from flask import current_app 7 | 8 | API_NAME = 'georef-ar-api' 9 | 10 | MAX_RESULT_LEN = current_app.config['MAX_RESULT_LEN'] 11 | MAX_RESULT_WINDOW = current_app.config['MAX_RESULT_WINDOW'] 12 | MAX_BULK_LEN = current_app.config['MAX_BULK_LEN'] 13 | ES_MULTISEARCH_MAX_LEN = current_app.config.get('ES_MULTISEARCH_MAX_LEN', 14 | MAX_RESULT_LEN) 15 | ES_TRACK_TOTAL_HITS = current_app.config.get('ES_TRACK_TOTAL_HITS') 16 | ADDRESS_PARSER_CACHE_SIZE = current_app.config['ADDRESS_PARSER_CACHE_SIZE'] 17 | 18 | ISCT_DOOR_NUM_TOLERANCE_M = 50 19 | BTWN_DOOR_NUM_TOLERANCE_M = 150 20 | BTWN_DISTANCE_TOLERANCE_M = 200 21 | 22 | MIN_AUTOCOMPLETE_CHARS = 4 23 | DEFAULT_SEARCH_SIZE = 10 24 | DEFAULT_FUZZINESS = 'AUTO:4,8' 25 | 26 | STATE_ID_LEN = 2 27 | DEPT_ID_LEN = 5 28 | MUNI_ID_LEN = 6 29 | CENSUS_LOCALITY_ID_LEN = 8 30 | SETTLEMENT_ID_LEN = (8, 10) 31 | LOCALITY_ID_LEN = (8, 10) 32 | STREET_ID_LEN = 13 33 | -------------------------------------------------------------------------------- /service/location.py: -------------------------------------------------------------------------------- 1 | """Módulo 'location.py' de georef-ar-api. 2 | 3 | Contiene las clases y funciones necesarias para la implementación del recurso 4 | /ubicacion de la API. 5 | """ 6 | 7 | from service.data import ElasticsearchSearch, StatesSearch, DepartmentsSearch 8 | from service.data import MunicipalitiesSearch 9 | from service import names as N 10 | from service.geometry import Point 11 | from service.query_result import QueryResult 12 | 13 | 14 | def _build_location_result(params, query, state, dept, muni): 15 | """Construye un resultado para una consulta al endpoint de ubicación. 16 | 17 | Args: 18 | params (dict): Parámetros recibidos. 19 | query (dict): Query utilizada para obtener los resultados (generada a 20 | partir de 'params'). 21 | state (dict): Provincia encontrada en la ubicación especificada. 22 | Puede ser None. 23 | dept (dict): Departamento encontrado en la ubicación especificada. 24 | Puede ser None. 25 | muni (dict): Municipio encontrado en la ubicación especificada. Puede 26 | ser None. 27 | 28 | Returns: 29 | QueryResult: Resultado de ubicación. 30 | 31 | """ 32 | empty_entity = { 33 | N.ID: None, 34 | N.NAME: None, 35 | N.SOURCE: None 36 | } 37 | 38 | if not state: 39 | # El punto no está en la República Argentina 40 | state = empty_entity.copy() 41 | dept = empty_entity.copy() 42 | muni = empty_entity.copy() 43 | else: 44 | dept = dept or empty_entity.copy() 45 | muni = muni or empty_entity.copy() 46 | 47 | return QueryResult.from_single_entity({ 48 | N.STATE: state, 49 | N.DEPT: dept, 50 | N.MUN: muni, 51 | N.LAT: query['lat'], 52 | N.LON: query['lon'] 53 | }, params) 54 | 55 | 56 | def run_location_queries(es, params_list, queries): 57 | """Dada una lista de queries de ubicación, construye las queries apropiadas 58 | a índices de departamentos y municipios, y las ejecuta utilizando 59 | Elasticsearch. 60 | 61 | Args: 62 | es (Elasticsearch): Conexión a Elasticsearch. 63 | params_list (list): Lista de ParametersParseResult. 64 | queries (list): Lista de queries de ubicación, generadas a partir de 65 | 'params_list'. 66 | 67 | Returns: 68 | list: Resultados de ubicaciones (QueryResult). 69 | 70 | """ 71 | # TODO: 72 | # Por problemas con los datos de origen, se optó por utilizar una 73 | # implementación simple para la la funcion 'run_location_queries'. 74 | # Cuando los datos de departamentos cubran todo el departamento nacional, 75 | # se podría modificar la función para que funcione de la siguiente forma: 76 | # 77 | # (Recordar que las provincias y departamentos cubren todo el territorio 78 | # nacional, pero no los municipios.) 79 | # 80 | # Por cada consulta, implementar un patrón similar al de address.py (con 81 | # iteradores de consultas), donde cada iterador ('búsqueda') realiza los 82 | # siguientes pasos: 83 | # 84 | # 1) Buscar la posición en el índice de departamentos. 85 | # 2) Si se obtuvo un departamento, buscar la posición nuevamente pero en el 86 | # índice de municipios. Si no se obtuvo nada, cancelar la búsqueda. 87 | # 3) Componer el departamento, la provincia del departamento y el municipio 88 | # en un QueryResult para completar la búsqueda. 89 | 90 | all_searches = [] 91 | 92 | state_searches = [] 93 | muni_searches = [] 94 | dept_searches = [] 95 | 96 | for query in queries: 97 | es_query = { 98 | 'geo_shape_geoms': [Point.from_json_location(query).to_geojson()], 99 | 'fields': [N.ID, N.NAME, N.SOURCE], 100 | 'size': 1 101 | } 102 | 103 | # Buscar la posición en provincias, departamentos y municipios 104 | 105 | search = StatesSearch(es_query) 106 | all_searches.append(search) 107 | state_searches.append(search) 108 | 109 | search = DepartmentsSearch(es_query) 110 | all_searches.append(search) 111 | dept_searches.append(search) 112 | 113 | search = MunicipalitiesSearch(es_query) 114 | all_searches.append(search) 115 | muni_searches.append(search) 116 | 117 | # Ejecutar todas las búsquedas preparadas 118 | ElasticsearchSearch.run_searches(es, all_searches) 119 | 120 | locations = [] 121 | iterator = zip(params_list, queries, state_searches, dept_searches, 122 | muni_searches) 123 | 124 | for params, query, state_search, dept_search, muni_search in iterator: 125 | # Ya que la query de tipo location retorna una o cero entidades, 126 | # extraer la primera entidad de los resultados, o tomar None si 127 | # no hay resultados. 128 | state = state_search.result.hits[0] if state_search.result else None 129 | dept = dept_search.result.hits[0] if dept_search.result else None 130 | muni = muni_search.result.hits[0] if muni_search.result else None 131 | 132 | result = _build_location_result(params.received_values(), query, state, 133 | dept, muni) 134 | locations.append(result) 135 | 136 | return locations 137 | -------------------------------------------------------------------------------- /service/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datosgobar/georef-ar-api/b1911b612214a52046f031bec922dd5cb5dd7fcf/service/management/__init__.py -------------------------------------------------------------------------------- /service/management/gunicorn_profile.py: -------------------------------------------------------------------------------- 1 | """gunicorn_profile.py - profiler para endpoints 2 | Requiere: git, imagemagick tools, graphviz 3 | 4 | Para utilizar, ejecutar el siguiente comando en la carpeta raíz del proyecto: 5 | 6 | $ make start_profile_server 7 | 8 | Luego, realizar una request HTTP a localhost:5000 para llevar a cabo un 9 | análisis de performance. Los resultados se depositan en el directorio 10 | 'profile'. 11 | """ 12 | 13 | import cProfile 14 | import pstats 15 | import io 16 | import subprocess 17 | import os 18 | import time 19 | import shutil 20 | 21 | MAX_ROWS = 20 22 | PROFILE_DIR = 'profile' 23 | 24 | 25 | def run_cmd(cmd, input_data=None): 26 | result = subprocess.run(cmd.split(), stdout=subprocess.PIPE, 27 | encoding='utf-8', check=True, input=input_data) 28 | return result.stdout 29 | 30 | 31 | def assert_command_exists(cmd): 32 | if not shutil.which(cmd): 33 | raise RuntimeError('The following command is required: {}'.format(cmd)) 34 | 35 | 36 | def when_ready(_): 37 | assert_command_exists('git') 38 | assert_command_exists('dot') 39 | assert_command_exists('gprof2dot') 40 | assert_command_exists('convert') 41 | 42 | if not os.path.exists(PROFILE_DIR): 43 | os.makedirs(PROFILE_DIR) 44 | 45 | 46 | def pre_request(worker, _): 47 | worker.profile = cProfile.Profile() 48 | worker.profile.enable() 49 | 50 | 51 | def post_request(worker, req, *_): 52 | worker.profile.disable() 53 | t = time.strftime('%Y.%m.%d-%H.%M.%S') 54 | s = io.StringIO() 55 | ps = pstats.Stats(worker.profile, stream=s) 56 | ps.sort_stats('time', 'cumulative') 57 | 58 | try: 59 | git_commit = run_cmd('git describe --always --tags --dirty') 60 | except subprocess.CalledProcessError: 61 | git_commit = 'unknown commit' 62 | 63 | base_name = os.path.join( 64 | PROFILE_DIR, 65 | '{}_{}'.format(req.path[1:].replace('/', '-'), t) 66 | ) 67 | 68 | # Guardar estadísticas de llamados a funciones 69 | with open(base_name + '_stats.txt', 'w') as f: 70 | print('{}?{}'.format(req.path, req.query), file=f) 71 | print(git_commit, file=f) 72 | ps.print_stats(MAX_ROWS) 73 | print(s.getvalue(), file=f) 74 | 75 | # Guardar objeto Stats 76 | dump_path = base_name + '_dump.bin' 77 | ps.dump_stats(dump_path) 78 | 79 | # Crear grafo de funciones con gprof2dot 80 | try: 81 | graph = run_cmd('gprof2dot -f pstats {}'.format(dump_path)) 82 | img_path = base_name + '_graph.png' 83 | run_cmd('dot -Tpng -o {}'.format(img_path), input_data=graph) 84 | 85 | # Agregar texto a la imagen creada 86 | cmd_template = 'convert ' + img_path + ' \ 87 | -pointsize 50 \ 88 | label:{txt} \ 89 | -gravity center \ 90 | -append ' + img_path 91 | 92 | run_cmd(cmd_template.format(txt=req.path)) 93 | if req.query: 94 | run_cmd(cmd_template.format(txt=req.query)) 95 | run_cmd(cmd_template.format(txt=git_commit)) 96 | 97 | except subprocess.CalledProcessError: 98 | print('No se pudo generar imagen utilizando gprof2dot y dot.') 99 | -------------------------------------------------------------------------------- /service/names.py: -------------------------------------------------------------------------------- 1 | """Módulo 'names' de georef-ar-api 2 | 3 | Declara los nombres que usa la API para campos, 4 | parámetros, y otras claves que se usan frecuentemente. 5 | 6 | Se utilizan variables globales (constantes) para permitir escribir código en 7 | inglés (e.g. FLOOR) con contenido real en español ('piso'). Esto es necesario 8 | ya que los datos producidos por georef-ar-etl están en español, así como la 9 | interfaz externa de georef-ar-api que consumen los usuarios. El uso de 10 | variables también permite que herramientas como jedi/pylint/flake8 detecten 11 | errores de uso de variables inexistentes estáticamente (en comparación a 12 | utilizar un diccionario de str-str). 13 | """ 14 | 15 | FIELDS_SEP = '.' 16 | 17 | 18 | def join(*words): 19 | return FIELDS_SEP.join(words) 20 | 21 | 22 | ########################## 23 | # Valores simples # 24 | ########################## 25 | 26 | # Entidades y recursos 27 | STATE = 'provincia' 28 | STATES = 'provincias' 29 | DEPARTMENTS = 'departamentos' 30 | DEPT = 'departamento' 31 | MUN = 'municipio' 32 | MUNICIPALITIES = 'municipios' 33 | CENSUS_LOCALITIES = 'localidades_censales' 34 | CENSUS_LOCALITY = 'localidad_censal' 35 | SETTLEMENTS = 'asentamientos' 36 | SETTLEMENT = 'asentamiento' 37 | LOCALITIES = 'localidades' 38 | LOCALITY = 'localidad' 39 | STREET = 'calle' 40 | STREET_A = 'calle_a' 41 | STREET_B = 'calle_b' 42 | STREET_X1 = 'calle_cruce_1' 43 | STREET_X2 = 'calle_cruce_2' 44 | STREETS = 'calles' 45 | STREET_BLOCKS = 'cuadras' 46 | INTERSECTION = 'interseccion' 47 | INTERSECTIONS = 'intersecciones' 48 | ADDRESS = 'direccion' 49 | ADDRESSES = 'direcciones' 50 | LOCATION = 'ubicacion' 51 | LOCATIONS = 'ubicaciones' 52 | RESULT = 'resultado' 53 | RESULTS = 'resultados' 54 | 55 | # Campos, parámetros, etc. 56 | BASIC = 'basico' 57 | CENTROID = 'centroide' 58 | COMPLETE = 'completo' 59 | DOOR_NUM = 'altura' 60 | END = 'fin' 61 | ISO_ID = 'iso_id' 62 | ISO_NAME = 'iso_nombre' 63 | COMPLETE_NAME = 'nombre_completo' 64 | EXACT = 'exacto' 65 | FIELDS = 'campos' 66 | ERROR = 'error' 67 | ERRORS = 'errores' 68 | FLATTEN = 'aplanar' 69 | FLOOR = 'piso' 70 | FORMAT = 'formato' 71 | FUNCTION = 'funcion' 72 | FULL_NAME = 'nomenclatura' 73 | GEOM = 'geometria' 74 | HELP = 'ayuda' 75 | CATEGORY = 'categoria' 76 | ID = 'id' 77 | ITEM = 'item' 78 | LAT = 'lat' 79 | LEFT = 'izquierda' 80 | LON = 'lon' 81 | MAX = 'max' 82 | NAME = 'nombre' 83 | OFFSET = 'inicio' 84 | ORDER = 'orden' 85 | PARAMETERS = 'parametros' 86 | QUANTITY = 'cantidad' 87 | RIGHT = 'derecha' 88 | SOURCE = 'fuente' 89 | STANDARD = 'estandar' 90 | START = 'inicio' 91 | TOTAL = 'total' 92 | TYPE = 'tipo' 93 | UNIT = 'unidad' 94 | VALUE = 'valor' 95 | 96 | ########################## 97 | # Valores compuestos # 98 | ########################## 99 | 100 | # Campos de entidades 101 | STATE_ID = join(STATE, ID) 102 | STATE_INTERSECTION = join(STATE, INTERSECTION) 103 | STATE_NAME = join(STATE, NAME) 104 | STATE_SOURCE = join(STATE, SOURCE) 105 | DEPT_ID = join(DEPT, ID) 106 | DEPT_NAME = join(DEPT, NAME) 107 | DEPT_SOURCE = join(DEPT, SOURCE) 108 | CENSUS_LOCALITY_ID = join(CENSUS_LOCALITY, ID) 109 | CENSUS_LOCALITY_NAME = join(CENSUS_LOCALITY, NAME) 110 | MUN_ID = join(MUN, ID) 111 | MUN_NAME = join(MUN, NAME) 112 | MUN_SOURCE = join(MUN, SOURCE) 113 | EXACT_SUFFIX = join('{}', EXACT) 114 | C_LAT = join(CENTROID, LAT) 115 | C_LON = join(CENTROID, LON) 116 | LOCATION_LAT = join(LOCATION, LAT) 117 | LOCATION_LON = join(LOCATION, LON) 118 | 119 | # Campos de altura 120 | START_L = join(DOOR_NUM, START, LEFT) 121 | START_R = join(DOOR_NUM, START, RIGHT) 122 | END_L = join(DOOR_NUM, END, LEFT) 123 | END_R = join(DOOR_NUM, END, RIGHT) 124 | DOOR_NUM_UNIT = join(DOOR_NUM, UNIT) 125 | DOOR_NUM_VAL = join(DOOR_NUM, VALUE) 126 | 127 | # Campos de calles 128 | STREET_ID = join(STREET, ID) 129 | STREET_NAME = join(STREET, NAME) 130 | STREET_CATEGORY = join(STREET, CATEGORY) 131 | STREET_X1_ID = join(STREET_X1, ID) 132 | STREET_X1_NAME = join(STREET_X1, NAME) 133 | STREET_X1_CATEGORY = join(STREET_X1, CATEGORY) 134 | STREET_X2_ID = join(STREET_X2, ID) 135 | STREET_X2_NAME = join(STREET_X2, NAME) 136 | STREET_X2_CATEGORY = join(STREET_X2, CATEGORY) 137 | 138 | ########################## 139 | # Plurales # 140 | ########################## 141 | 142 | _PLURALS = { 143 | STATE: STATES, 144 | DEPT: DEPARTMENTS, 145 | MUN: MUNICIPALITIES, 146 | CENSUS_LOCALITY: CENSUS_LOCALITIES, 147 | SETTLEMENT: SETTLEMENTS, 148 | LOCALITY: LOCALITIES, 149 | STREET: STREETS, 150 | ADDRESS: ADDRESSES, 151 | LOCATION: LOCATIONS, 152 | RESULT: RESULTS, 153 | ERROR: ERRORS, 154 | INTERSECTION: INTERSECTIONS 155 | } 156 | 157 | _SINGULARS = {value: key for key, value in _PLURALS.items()} 158 | 159 | 160 | def plural(word): 161 | if word not in _PLURALS: 162 | raise RuntimeError('No plural defined for: {}'.format(word)) 163 | 164 | return _PLURALS[word] 165 | 166 | 167 | def singular(word): 168 | if word not in _SINGULARS: 169 | raise RuntimeError('No singular defined for: {}'.format(word)) 170 | 171 | return _SINGULARS[word] 172 | -------------------------------------------------------------------------------- /service/query_result.py: -------------------------------------------------------------------------------- 1 | """Módulo 'query_result' de georef-ar-api. 2 | 3 | Contiene la especificación de la clase 'QueryResult' para uso durante el 4 | proceso de normalización de datos. 5 | """ 6 | 7 | 8 | class QueryResult: 9 | """Representa el resultado de una consulta a la API. Los contenidos del 10 | resultado no necesariamente deben haber sido obtenidos desde Elasticsearch, 11 | pueden haber sido generados vía código. En ambos casos, el uso de 12 | QueryResult es idéntico. 13 | 14 | Se distinguen dos casos de resultados posibles: 15 | 1) Resultados en forma de lista de 0 o más elementos. 16 | 2) Resultado singular. 17 | Internamente, ambos casos se almacenan como una lista. 18 | 19 | Attributes: 20 | _entities (list): Lista de entidades (provincias, municipios, 21 | ubicaciones, calles, etc.). 22 | _params (dict): Parámetros de consulta que generaron este objeto 23 | 'QueryResult'. 24 | _iterable (bool): Falso si el resultado representa una entidad 25 | singular (como en el caso de una ubicación). Verdadero cuando se 26 | representa una lista de entidades (como en el caso de, por ejemplo, 27 | provincias). 28 | _total (int): Total de entidades encontradas, no necesariamente 29 | incluidas en la respuesta. En caso de iterable == False, se utiliza 30 | 1 como valor default, ya que el 'total' de entidades posibles a ser 31 | devueltas es 0 o 1, pero al contar ya con un resultado, el número 32 | deber ser 1. 33 | _offset (int): Cantidad de resultados salteados. En caso de iterable == 34 | False, se establece como 0, ya que no se puede saltear el único 35 | posible. 36 | 37 | """ 38 | 39 | __slots__ = ['_entities', '_params', '_iterable', '_total', '_offset'] 40 | 41 | def __init__(self, entities, params, iterable=False, total=1, offset=0): 42 | """Inicializar una QueryResult. Se recomienda utilizar 43 | 'from_single_entity' y 'from_entity_list' en vez de utilizar este 44 | método. 45 | 46 | Args: 47 | entities (list): Ver atributo '_entities'. 48 | params (dict): Ver atributo '_params'. 49 | iterable (bool): Ver atributo '_iterable'. 50 | total (int): Ver atributo '_total'. 51 | offset (int): Ver atributo '_offset'. 52 | 53 | """ 54 | self._entities = entities 55 | self._params = params 56 | self._iterable = iterable 57 | self._total = total 58 | self._offset = offset 59 | 60 | @classmethod 61 | def from_single_entity(cls, entity, params): 62 | """Construir una QueryResult a partir de una entidad singular. 63 | 64 | Args: 65 | entity (dict): Entidad encontrada. 66 | params (dict): Parámetros de consulta. 67 | 68 | Returns: 69 | QueryResult: objeto construido. 70 | 71 | """ 72 | return cls([entity], params) 73 | 74 | @classmethod 75 | def from_entity_list(cls, entities, params, total, offset=0): 76 | """Construir una QueryResult a partir de una lista de entidades de 77 | cualquier longitud. 78 | 79 | Args: 80 | entities (list): Lista de entidades. 81 | params (dict): Parámetros de consulta. 82 | total (int): Total de entidades encontradas, no necesariamente 83 | incluidas. 84 | offset (int): Cantidad de resultados salteados. 85 | 86 | Returns: 87 | QueryResult: objeto construido. 88 | 89 | """ 90 | return cls(entities, params, iterable=True, total=total, offset=offset) 91 | 92 | @classmethod 93 | def empty(cls, params): 94 | """Construir una QueryResult vacía. 95 | 96 | Args: 97 | params (dict): Parámetros de consulta. 98 | 99 | Returns: 100 | QueryResult: objeto construido. 101 | 102 | """ 103 | return cls.from_entity_list([], params, total=0) 104 | 105 | @property 106 | def entities(self): 107 | return self._entities 108 | 109 | @property 110 | def params(self): 111 | return self._params 112 | 113 | def first_entity(self): 114 | return self._entities[0] 115 | 116 | @property 117 | def total(self): 118 | return self._total 119 | 120 | @property 121 | def offset(self): 122 | return self._offset 123 | 124 | @property 125 | def iterable(self): 126 | return self._iterable 127 | -------------------------------------------------------------------------------- /service/routes.py: -------------------------------------------------------------------------------- 1 | """Módulo 'routes' de georef-ar-api 2 | 3 | Declara las rutas de los recursos que expone la API e 4 | invoca las funciones que procesan dichos recursos. 5 | """ 6 | 7 | from functools import wraps 8 | from flask import current_app, request, redirect, Blueprint 9 | from service import app, normalizer, formatter 10 | from service import names as N 11 | 12 | 13 | def disable_cache(f): 14 | """Dada una función que maneja una request HTTP, modifica los valores de 15 | los headers para deshabilitar el cacheo de respuestas. 16 | 17 | Args: 18 | f (function): Función utilizada para manejar un endpoint HTTP de flask. 19 | 20 | Returns: 21 | function: Endpoint con decorador aplicado. 22 | 23 | """ 24 | @wraps(f) 25 | def decorated_func(*args, **kwargs): 26 | resp = f(*args, **kwargs) 27 | resp.cache_control.no_cache = True 28 | return resp 29 | 30 | return decorated_func 31 | 32 | 33 | def add_complete_downloads(bp, urls): 34 | """Agrega endpoints de descarga completa de datos a un Flask Blueprint. 35 | 36 | Args: 37 | bp (flask.Blueprint): Objeto donde agregar los endpoints de descarga. 38 | urls (dict): Diccionario con tipos de entidades como claves, y 39 | diccionarios como valores. Cada subdiccionario debe contener, por 40 | cada formato (CSV, JSON, GEOJSON), una URL a donde redirigir (o 41 | None para no agregar el endpoint). Ver el archivo 42 | georef.example.cfg para más detalles. 43 | 44 | """ 45 | entities = [N.STATES, N.DEPARTMENTS, N.MUNICIPALITIES, 46 | N.CENSUS_LOCALITIES.replace('_', '-'), N.SETTLEMENTS, 47 | N.LOCALITIES, N.STREETS, N.STREET_BLOCKS] 48 | formats = ['json', 'csv', 'geojson', 'ndjson'] 49 | 50 | for entity in entities: 51 | entity_urls = urls[entity] 52 | 53 | for fmt in formats: 54 | url = entity_urls.get(fmt) 55 | if url: 56 | # e.g: /provincias.csv 57 | endpoint = '{}-{}'.format(entity, fmt) 58 | rule = '/{}.{}'.format(entity, fmt) 59 | 60 | bp.add_url_rule(rule, endpoint, 61 | lambda location=url: redirect(location)) 62 | 63 | 64 | @app.errorhandler(404) 65 | def handle_404(_): 66 | return formatter.create_404_error_response() 67 | 68 | 69 | @app.errorhandler(405) 70 | def handle_405(_): 71 | return formatter.create_405_error_response(app.url_map) 72 | 73 | 74 | # API v1.0 75 | bp_v1_0 = Blueprint('georef_v1.0', __name__) 76 | 77 | add_complete_downloads(bp_v1_0, current_app.config['COMPLETE_DOWNLOAD_URLS']) 78 | 79 | 80 | @bp_v1_0.route('/provincias', methods=['GET', 'POST']) 81 | def get_states(): 82 | return normalizer.process_state(request) 83 | 84 | 85 | @bp_v1_0.route('/departamentos', methods=['GET', 'POST']) 86 | def get_departments(): 87 | return normalizer.process_department(request) 88 | 89 | 90 | @bp_v1_0.route('/municipios', methods=['GET', 'POST']) 91 | def get_municipalities(): 92 | return normalizer.process_municipality(request) 93 | 94 | 95 | @bp_v1_0.route('/localidades-censales', methods=['GET', 'POST']) 96 | def get_census_localities(): 97 | return normalizer.process_census_locality(request) 98 | 99 | 100 | @bp_v1_0.route('/asentamientos', methods=['GET', 'POST']) 101 | def get_settlements(): 102 | return normalizer.process_settlement(request) 103 | 104 | 105 | @bp_v1_0.route('/localidades', methods=['GET', 'POST']) 106 | def get_localities(): 107 | return normalizer.process_locality(request) 108 | 109 | 110 | @bp_v1_0.route('/calles', methods=['GET', 'POST']) 111 | def get_streets(): 112 | return normalizer.process_street(request) 113 | 114 | 115 | @bp_v1_0.route('/direcciones', methods=['GET', 'POST']) 116 | def get_addresses(): 117 | return normalizer.process_address(request) 118 | 119 | 120 | @bp_v1_0.route('/ubicacion', methods=['GET', 'POST']) 121 | @disable_cache 122 | def get_location(): 123 | return normalizer.process_location(request) 124 | 125 | 126 | # Última versión de la API 127 | app.register_blueprint(bp_v1_0, url_prefix='/api') 128 | 129 | # v1.0 130 | app.register_blueprint(bp_v1_0, url_prefix='/api/v1.0') 131 | -------------------------------------------------------------------------------- /service/street.py: -------------------------------------------------------------------------------- 1 | """Módulo 'street' de georef-ar-api. 2 | 3 | Contiene funciones y clases utilizadas para normalizar calles (recurso 4 | /calles). Este módulo puede ser considerado una extensión del módulo 5 | 'normalizer', con funciones específicas para el procesamiento de calles. 6 | 7 | Notar que la búsqueda de calles (/calles) no es la búsqueda de direcciones 8 | (/direcciones). La búsqueda de direcciones es más compleja ya que implica 9 | interpretar las distintas partes de una dirección, luego buscar una calle que 10 | contenga su altura, y posiblemente georreferenciarla. La búsqueda de calles es 11 | simplemente buscar listados de calles por nombre, ID y otros criterios. 12 | """ 13 | 14 | from service import data 15 | from service.query_result import QueryResult 16 | from service import names as N 17 | 18 | 19 | def run_street_queries(es, params_list, queries, formats): 20 | """Punto de entrada del módulo 'street.py'. Toma una lista de consultas de 21 | calles y las ejecuta, devolviendo los resultados QueryResult. 22 | 23 | Args: 24 | es (Elasticsearch): Conexión a Elasticsearch. 25 | params_list (list): Lista de ParametersParseResult. 26 | queries (list): Lista de búsquedas, generadas a partir de 27 | 'params_list'. 28 | formats (list): Lista de parámetros de formato de cada búsqueda, en 29 | forma de diccionario. 30 | 31 | Returns: 32 | list: Lista de QueryResult, una por cada búsqueda. 33 | 34 | """ 35 | 36 | searches = [] 37 | for query, fmt in zip(queries, formats): 38 | processed_query = query.copy() 39 | 40 | if N.FULL_NAME in fmt[N.FIELDS]: 41 | # La nomenclatura incluye el nombre de la provincia y del depto., 42 | # agregar esos campos a la query para luego poder extraer sus 43 | # nombres. 44 | processed_query['fields'] += (N.STATE, N.DEPT) 45 | 46 | searches.append(data.StreetsSearch(processed_query)) 47 | 48 | data.ElasticsearchSearch.run_searches(es, searches) 49 | 50 | for search, fmt in zip(searches, formats): 51 | if N.FULL_NAME in fmt[N.FIELDS]: 52 | # Agregar nomenclatura a cada hit del resultado. 53 | for hit in search.result.hits: 54 | full_name = '{}, {}, {}'.format( 55 | hit[N.NAME], hit[N.DEPT][N.NAME], hit[N.STATE][N.NAME] 56 | ) 57 | hit[N.FULL_NAME] = full_name 58 | 59 | return [ 60 | QueryResult.from_entity_list(search.result.hits, 61 | params.received_values(), 62 | search.result.total, 63 | search.result.offset) 64 | for search, params in zip(searches, params_list) 65 | ] 66 | -------------------------------------------------------------------------------- /service/strings.py: -------------------------------------------------------------------------------- 1 | """Módulo 'strings' de georef-ar-api 2 | 3 | Contiene mensajes de error en forma de texto para usuarios. 4 | """ 5 | 6 | ADDRESS_FORMAT = 'La dirección debe seguir alguno de los formatos listados \ 7 | bajo la clave \'ayuda\'.' 8 | ADDRESS_FORMAT_HELP = [ 9 | '', 10 | ' ' 11 | ] 12 | STRING_EMPTY = 'El campo no tiene contenido.' 13 | INT_VAL_ERROR = 'El parámetro no es un número entero.' 14 | FLOAT_VAL_ERROR = 'El parámetro no es un número real.' 15 | INVALID_CHOICE = 'El parámetro debe tomar el valor de uno de los listados \ 16 | bajo la clave \'ayuda\'.' 17 | INVALID_BULK = 'Las operaciones deben estar contenidas en una lista no vacía \ 18 | bajo la clave \'{}\'.' 19 | BULK_QS_INVALID = 'No se permiten parámetros vía query string en operaciones \ 20 | bulk.' 21 | INVALID_BULK_ENTRY = 'Las operaciones bulk deben ser de tipo objeto.' 22 | INTERNAL_ERROR = 'Ocurrió un error interno de servidor al procesar la \ 23 | petición.' 24 | MISSING_ERROR = 'El parámetro \'{}\' es obligatorio.' 25 | UNKNOWN_ERROR = 'El parámetro especificado no existe. Los parámetros \ 26 | aceptados están listados bajo la clave \'ayuda\'.' 27 | REPEATED_ERROR = 'El parámetro está repetido.' 28 | BULK_LEN_ERROR = 'El número máximo de operaciones bulk es: {}.' 29 | INT_VAL_SMALL = 'El número debe ser igual o mayor que {}.' 30 | INT_VAL_BIG = 'El número debe ser menor o igual que {}.' 31 | INT_VAL_BIG_GLOBAL = 'La suma de parámetros {} debe ser menor o igual \ 32 | que {}.' 33 | NOT_FOUND = 'No se encontró la URL especificada.' 34 | NOT_ALLOWED = 'Método no permitido en el recurso seleccionado.' 35 | ID_PARAM_INVALID = 'Cada ID debe ser numérico y de longitud {}.' 36 | ID_TWO_LENGTH_PARAM_INVALID = 'Cada ID debe ser numérico y de longitud {} ó {}.' 37 | ID_ALPHAMERIC_TWO_LENGTH_PARAM_INVALID = 'Cada ID debe ser de longitud {} ó {}.' 38 | ID_PARAM_LENGTH = 'La cantidad de ID debe ser menor o igual que {}.' 39 | ID_PARAM_UNIQUE = 'La lista no debe contener ID repetidos (ID repetido: {}).' 40 | COMPOUND_PARAM_ERROR = 'El valor del parámetro no es válido.' 41 | FIELD_LIST_EMPTY = 'La lista no contiene valores.' 42 | FIELD_LIST_REPEATED = 'La lista contiene valores repetidos.' 43 | FIELD_LIST_INVALID_CHOICE = 'El parámetro debe consistir en una lista de \ 44 | ítems separados por comas. Los valores posibles de los ítems se listan bajo \ 45 | la clave \'ayuda\'. Alternativamente, se pueden especificar los valores \ 46 | \'basico\', \'estandar\' o \'completo\'.' 47 | FIELD_INTERSECTION_FORMAT = 'El parámetro debe seguir el siguiente formato: \ 48 | :, :, ... (ver ejemplos bajo la \ 49 | clave ayuda).' 50 | FIELD_INTERSECTION_FORMAT_HELP = [ 51 | 'provincia:94:38', 52 | 'municipio:740038, departamento:74049', 53 | 'departamento:62035:62007:62084', 54 | 'municipio:700070:700049, provincia:02', 55 | 'departamento:14028' 56 | ] 57 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # georef-ar-api - Tests `unittest` 2 | 3 | El directorio `tests/` de `georef-ar-api` contiene todos los tests unitarios utilizados para comprobar que la API está funcionando correctamente luego de realizar cambios al código. Los tests están separados en dos categorías principales, indicadas en el comienzo del nombre de cada archivo: 4 | 5 | - **Tests Mock/Offline**: Estos tests pueden ser ejecutados sin contar con una instancia de Elasticsearch corriendo. No utilizan datos reales. Los tests de esta categoría cuentan con nombres de archivo de la forma `test_mock_{}.py`, y siempre heredan de la clase `GeorefMockTest`. 6 | 7 | - **Tests En Vivo/Online**: Estos tests requieren de una instancia de Elasticsearch corriendo, con los datos del ETL cargados correctamente. Los tests de esta categoría cuentan con nombres de archivo de la forma `test_search_{}.py`, y siempre heredan de la clase `GeorefLiveTest`. Si algún dato cambia de forma tal que rompa un test, se debe modificar el test para que funcione correctamente de nuevo, teniendo en cuenta de que debe seguir validando la misma parte de la funcionalidad de la API. 8 | 9 | Para ejecutar todos los tests desde la raíz del proyecto, utilizar: 10 | ```bash 11 | $ make test 12 | ``` 13 | 14 | Para ejecutar solo los tests que **no** requieren Elasticsearch: 15 | ```bash 16 | $ make test_mock 17 | ``` 18 | -------------------------------------------------------------------------------- /tests/test_mock_data.py: -------------------------------------------------------------------------------- 1 | import random 2 | from . import GeorefMockTest 3 | 4 | 5 | class DataMockTest(GeorefMockTest): 6 | def test_offset_value(self): 7 | """El valor de 'inicio' de las respuestas debería ser idéntico al que 8 | se especifica vía el parámetro 'inicio'. 9 | """ 10 | self.set_msearch_results([ 11 | { 12 | 'id': '06091010009', 13 | 'nombre': 'RANELAGH' 14 | } 15 | ]) 16 | offset = random.randint(10, 100) 17 | 18 | resp = self.get_response( 19 | return_value='full', 20 | url='/api/localidades?inicio={}&campos=id'.format(offset) 21 | ) 22 | 23 | self.assertEqual(resp['inicio'], offset) 24 | -------------------------------------------------------------------------------- /tests/test_mock_formatter.py: -------------------------------------------------------------------------------- 1 | from service import formatter 2 | from . import GeorefMockTest 3 | 4 | 5 | class FormattingTest(GeorefMockTest): 6 | def test_fields_list_to_dict(self): 7 | """El resultado de fields_list_to_dict debería ser un diccionario 8 | equivalente a los valores de una lista, desaplanados.""" 9 | fields = ( 10 | 'id', 11 | 'nombre', 12 | 'provincia.id', 13 | 'provincia.nombre', 14 | 'ubicacion.lat', 15 | 'ubicacion.lon', 16 | 'prueba.foo.bar', 17 | 'prueba.foo.baz' 18 | ) 19 | 20 | fields_dict = formatter.fields_list_to_dict(fields) 21 | self.assertEqual(fields_dict, { 22 | 'id': True, 23 | 'nombre': True, 24 | 'provincia': { 25 | 'id': True, 26 | 'nombre': True 27 | }, 28 | 'ubicacion': { 29 | 'lat': True, 30 | 'lon': True 31 | }, 32 | 'prueba': { 33 | 'foo': { 34 | 'bar': True, 35 | 'baz': True 36 | } 37 | } 38 | }) 39 | 40 | def test_flatten_dict(self): 41 | """Se debería aplanar un diccionario correctamente.""" 42 | original = { 43 | 'provincia': { 44 | 'id': '06', 45 | 'nombre': 'BUENOS AIRES' 46 | }, 47 | 'foo': 'bar' 48 | } 49 | 50 | formatter.flatten_dict(original) 51 | self.assertEqual(original, { 52 | 'provincia_id': '06', 53 | 'provincia_nombre': 'BUENOS AIRES', 54 | 'foo': 'bar' 55 | }) 56 | 57 | def test_flatten_dict_max_depth(self): 58 | """El aplanado de diccionarios debería fallar con diccionarios 59 | demasiado profundos.""" 60 | deep_dict = { 61 | 'a': { 62 | 'b': { 63 | 'c': { 64 | 'd': {} 65 | } 66 | } 67 | } 68 | } 69 | 70 | with self.assertRaises(RuntimeError): 71 | formatter.flatten_dict(deep_dict) 72 | 73 | def test_flatten_dict_max_depth_circular(self): 74 | """La conversión a XML debería fallar con diccionarios o listas 75 | con referencias circulares.""" 76 | c_dict = {} 77 | c_dict['a'] = c_dict 78 | 79 | with self.assertRaises(RuntimeError): 80 | formatter.flatten_dict(c_dict) 81 | 82 | def test_filter_result_fields(self): 83 | """Se debería poder filtrar los campos de un diccionario, utilizando 84 | otro diccionario para especificar cuáles campos deberían ser 85 | mantenidos.""" 86 | result = { 87 | 'simple': 'foo', 88 | 'removed': 'foo', 89 | 'nested': { 90 | 'field1': 'foo', 91 | 'field2': 'foo', 92 | 'removed': 'foo', 93 | 'nested2': { 94 | 'field1': 'foo', 95 | 'removed': 'foo' 96 | } 97 | } 98 | } 99 | 100 | fields = ( 101 | 'simple', 102 | 'nested.field1', 103 | 'nested.field2', 104 | 'nested.nested2.field1' 105 | ) 106 | 107 | formatter.filter_result_fields(result, 108 | formatter.fields_list_to_dict(fields)) 109 | self.assertEqual(result, { 110 | 'simple': 'foo', 111 | 'nested': { 112 | 'field1': 'foo', 113 | 'field2': 'foo', 114 | 'nested2': { 115 | 'field1': 'foo' 116 | } 117 | } 118 | }) 119 | 120 | def test_xml_max_depth(self): 121 | """La conversión a XML debería fallar con diccionarios demasiado 122 | profundos.""" 123 | deep_dict = { 124 | 'a': { 125 | 'b': { 126 | 'c': { 127 | 'd': {} 128 | } 129 | } 130 | } 131 | } 132 | 133 | with self.assertRaises(RuntimeError): 134 | formatter.value_to_xml('test', deep_dict, max_depth=3) 135 | 136 | def test_xml_max_depth_circular(self): 137 | """La conversión a XML debería fallar con diccionarios o listas 138 | con referencias circulares.""" 139 | c_dict = {} 140 | c_dict['a'] = c_dict 141 | 142 | with self.assertRaises(RuntimeError): 143 | formatter.value_to_xml('test', c_dict) 144 | 145 | def test_xml_structure(self): 146 | """El nodo raíz de todas las respuestas XML debería ser el tag 147 | 'georef-ar-api'.""" 148 | self.set_msearch_results([]) 149 | resp = self.get_response(params={'formato': 'xml'}, 150 | endpoint='/api/provincias', 151 | entity='provincias') 152 | 153 | self.assertEqual(resp.tag, 'georef-ar-api') 154 | -------------------------------------------------------------------------------- /tests/test_mock_lfu_dict.py: -------------------------------------------------------------------------------- 1 | from service.utils import LFUDict 2 | from . import GeorefMockTest 3 | 4 | DEFAULT_SIZE = 10 5 | 6 | 7 | class LFUDictTest(GeorefMockTest): 8 | def setUp(self): 9 | self.lfu_dict = LFUDict(DEFAULT_SIZE) 10 | super().setUp() 11 | 12 | def test_insert_read(self): 13 | """Los diccionarios LFU deberían aceptar las operaciones de 14 | inserción y consulta.""" 15 | self.lfu_dict['foo'] = 'bar' 16 | self.lfu_dict['test'] = 'working' 17 | 18 | self.assertTrue(self.lfu_dict['foo'] == 'bar' and 19 | self.lfu_dict['test'] == 'working', self.lfu_dict) 20 | 21 | def test_contains(self): 22 | """Los diccionarios LFU deberían aceptar la operación de consulta de 23 | presencia de una key.""" 24 | self.lfu_dict['foo'] = 'bar' 25 | 26 | self.assertTrue('foo' in self.lfu_dict, self.lfu_dict) 27 | 28 | def test_len(self): 29 | """Los diccionarios LFU deberían aceptar la operación de consulta de 30 | cantidad de ítems.""" 31 | self.lfu_dict['foo'] = 'bar' 32 | self.lfu_dict['test'] = 'working' 33 | self.lfu_dict['baz'] = 'qux' 34 | 35 | self.assertEqual(len(self.lfu_dict), 3, self.lfu_dict) 36 | 37 | def test_max_len(self): 38 | """Los diccionarios LFU nunca deberían tener más ítems que 'size'.""" 39 | for i in range(DEFAULT_SIZE + 10): 40 | self.lfu_dict['key{}'.format(i)] = i 41 | 42 | self.assertEqual(len(self.lfu_dict), DEFAULT_SIZE, self.lfu_dict) 43 | 44 | def test_key_deleted_score_0(self): 45 | """Los diccionarios LFU deberían eliminar las keys menos utilizadas.""" 46 | lfu_dict = LFUDict(2) 47 | lfu_dict['foo'] = 'foo' 48 | lfu_dict['bar'] = 'bar' 49 | lfu_dict['quux'] = 'quux' 50 | lfu_dict['quux'] # pylint: disable=pointless-statement 51 | lfu_dict['quuz'] = 'quuz' 52 | 53 | self.assertTrue('foo' not in lfu_dict and 'bar' not in lfu_dict, 54 | lfu_dict) 55 | 56 | def test_key_deleted(self): 57 | """Los diccionarios LFU deberían eliminar las keys menos utilizadas, 58 | incluso cuando todas las keys fueron accedidas una vez o más.""" 59 | lfu_dict = LFUDict(2) 60 | lfu_dict['foo'] = 'foo' 61 | for _ in range(3): 62 | lfu_dict['foo'] # pylint: disable=pointless-statement 63 | 64 | lfu_dict['bar'] = 'bar' 65 | for _ in range(4): 66 | lfu_dict['bar'] # pylint: disable=pointless-statement 67 | 68 | lfu_dict['quuz'] = 'quuz' 69 | 70 | self.assertTrue('foo' not in lfu_dict and 'bar' in lfu_dict, 71 | lfu_dict) 72 | -------------------------------------------------------------------------------- /tests/test_mock_normalizer.py: -------------------------------------------------------------------------------- 1 | import random 2 | import elasticsearch 3 | from . import GeorefMockTest 4 | 5 | 6 | ENDPOINTS = [ 7 | '/calles', 8 | '/provincias', 9 | '/departamentos', 10 | '/municipios', 11 | '/localidades' 12 | ] 13 | 14 | 15 | class NormalizerTest(GeorefMockTest): 16 | def setUp(self): 17 | self.base_url = '/api/v1.0' 18 | super().setUp() 19 | 20 | def test_elasticsearch_connection_error(self): 21 | """Se debería devolver un error 500 cuando falla la conexión a 22 | Elasticsearch.""" 23 | self.es.side_effect = elasticsearch.ElasticsearchException() 24 | self.assert_500_error(random.choice(ENDPOINTS)) 25 | 26 | def test_elasticsearch_msearch_error(self): 27 | """Se debería devolver un error 500 cuando falla la query 28 | MultiSearch.""" 29 | self.es.return_value.msearch.side_effect = \ 30 | elasticsearch.ElasticsearchException() 31 | self.assert_500_error(random.choice(ENDPOINTS)) 32 | 33 | def test_elasticsearch_msearch_results_error(self): 34 | """Se debería devolver un error 500 cuando falla la query 35 | MultiSearch (retorna errores).""" 36 | self.es.return_value.msearch.return_value = { 37 | 'responses': [ 38 | { 39 | 'error': { 40 | 'type': 'mock', 41 | 'reason': 'mock' 42 | } 43 | } 44 | ] 45 | } 46 | 47 | self.assert_500_error(random.choice(ENDPOINTS)) 48 | 49 | def assert_500_error(self, url): 50 | resp = self.app.get(self.base_url + url) 51 | self.assertTrue(resp.status_code == 500 and 'errores' in resp.json) 52 | -------------------------------------------------------------------------------- /tests/test_mock_point.py: -------------------------------------------------------------------------------- 1 | from service.geometry import Point 2 | from . import GeorefMockTest 3 | 4 | POINT_1 = Point(-58.381614, -34.603713) # Obelisco 5 | POINT_2 = Point(-58.373691, -34.608874) # Cabildo 6 | POINT_3 = Point(-58.373720, -34.592172) # Torre Monumental - Retiro 7 | 8 | DISTANCES = [ 9 | (POINT_1, POINT_2, 920), 10 | (POINT_1, POINT_3, 1470), 11 | (POINT_2, POINT_3, 1855) 12 | ] 13 | 14 | 15 | class PointTest(GeorefMockTest): 16 | def test_distance_0(self): 17 | """La distancia entre dos puntos iguales debería ser 0.""" 18 | point = Point(0, 0) 19 | self.assertAlmostEqual(point.approximate_distance_meters(point), 0) 20 | 21 | def test_distances(self): 22 | """La distancia entre dos puntos conocidos debe ser correcta.""" 23 | for p1, p2, distance in DISTANCES: 24 | calculated = p1.approximate_distance_meters(p2) 25 | self.assertAlmostEqual(distance, calculated, delta=5) 26 | 27 | def test_midpoint(self): 28 | """El punto medio entre dos puntos debe ser correcto.""" 29 | p1 = Point(0, 0) 30 | p2 = Point(10, 10) 31 | midpoint = p1.midpoint(p2) 32 | self.assertAlmostEqual(midpoint.lat, 5) 33 | self.assertAlmostEqual(midpoint.lon, 5) 34 | -------------------------------------------------------------------------------- /tests/test_mock_response.py: -------------------------------------------------------------------------------- 1 | from service import formatter 2 | from . import GeorefMockTest 3 | 4 | 5 | class ResponsesTest(GeorefMockTest): 6 | def setUp(self): 7 | super().setUp() 8 | self.set_msearch_results([]) 9 | 10 | def test_params_present(self): 11 | """Los parámetros enviados a la API deberían estar presentes bajo el 12 | valor 'parametros'.""" 13 | resp = self.get_response( 14 | return_value='full', 15 | endpoint='/api/provincias', 16 | params={ 17 | 'id': '02', 18 | 'max': 1, 19 | 'aplanar': True, 20 | 'orden': 'id', 21 | 'interseccion': 'departamento:02000', 22 | 'campos': 'nombre, id' 23 | } 24 | ) 25 | 26 | resp['parametros']['campos'].sort() 27 | self.assertDictEqual(resp['parametros'], { 28 | 'id': ['02'], 29 | 'max': 1, 30 | 'aplanar': True, 31 | 'orden': 'id', 32 | 'interseccion': { 33 | 'departamentos': ['02000'] 34 | }, 35 | 'campos': ['id', 'nombre'] 36 | }) 37 | 38 | def test_params_present_address(self): 39 | """Los parámetros enviados a la API deberían estar presentes bajo el 40 | valor 'parametros' (/direcciones).""" 41 | resp = self.get_response( 42 | return_value='full', 43 | endpoint='/api/direcciones', 44 | params={ 45 | 'direccion': 'Mitre N° 33 2B e/ Calle 11 y Sarmiento', 46 | 'max': 1, 47 | 'provincia': '6', 48 | 'formato': 'json' 49 | } 50 | ) 51 | 52 | self.assertDictEqual(resp['parametros'], { 53 | 'direccion': { 54 | 'altura': { 55 | 'unidad': 'N°', 56 | 'valor': '33' 57 | }, 58 | 'piso': '2B', 59 | 'calles': ['Mitre', 'Calle 11', 'Sarmiento'], 60 | 'tipo': 'entre_calles' 61 | }, 62 | 'max': 1, 63 | 'provincia': ['06'], 64 | 'formato': 'json' 65 | }) 66 | 67 | def test_params_present_xml(self): 68 | """Los parámetros enviados a la API deberían estar presentes bajo el 69 | valor 'parametros' (formato XML).""" 70 | params = { 71 | 'id': '1401401027080', 72 | 'max': 1, 73 | 'aplanar': True, 74 | 'orden': 'id', 75 | 'interseccion': 'departamento:02000' 76 | } 77 | 78 | json_resp = self.get_response(endpoint='/api/calles', params=params, 79 | return_value='full') 80 | json_resp['parametros']['formato'] = 'xml' 81 | json_as_xml = formatter.value_to_xml('parametros', 82 | json_resp['parametros'], 83 | list_item_default='item') 84 | 85 | params['formato'] = 'xml' 86 | xml_resp = self.get_response(endpoint='/api/calles', params=params) 87 | xml_params = xml_resp.find('resultado').find('parametros') 88 | 89 | self.assert_xml_equal(json_as_xml, xml_params) 90 | -------------------------------------------------------------------------------- /tests/test_mock_routes.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | from . import GeorefMockTest 4 | 5 | EXAMPLE_CONFIG = 'config/georef.example.cfg' 6 | 7 | 8 | class RoutesTest(GeorefMockTest): 9 | def test_v1_0_endpoints(self): 10 | """Los endpoints con prefijo /api/v1.0 deberían existir incluso si no 11 | se cuenta con más de una versión de la API. Esto se debe a que 12 | versiones iniciales de la API fueron publicadas que utilizaban el 13 | prefijo /v1.0.""" 14 | urls = [ 15 | '/api/v1.0/provincias', 16 | '/api/v1.0/departamentos', 17 | '/api/v1.0/municipios', 18 | '/api/v1.0/localidades', 19 | '/api/v1.0/direcciones', 20 | '/api/v1.0/calles', 21 | '/api/v1.0/ubicacion' 22 | ] 23 | 24 | validations = [ 25 | self.app.options(url).status_code == 200 26 | for url in urls 27 | ] 28 | 29 | self.assertTrue(all(validations)) 30 | 31 | @unittest.skipIf(os.environ['GEOREF_CONFIG'] != EXAMPLE_CONFIG, 32 | 'No se está utilizando la config de ejemplo') 33 | def test_complete_download_redirect(self): 34 | """La API debería permitir la descarga total de datos por recurso. Las 35 | descargas se implementan como una redirección a una URL donde se 36 | almacenan los datos a descargarse (HTTP 302). 37 | 38 | La configuración de ejemplo de la API utiliza una URL de ejemplo para 39 | /provincias.json.""" 40 | resp = self.app.get('/api/provincias.json') 41 | self.assertTrue(resp.status_code == 302 and 42 | resp.headers['Location'] == 'https://www.example.org') 43 | 44 | @unittest.skipIf(os.environ['GEOREF_CONFIG'] != EXAMPLE_CONFIG, 45 | 'No se está utilizando la config de ejemplo') 46 | def test_complete_download_redirect_unset(self): 47 | """Si no se configura uno de los recursos de descarga completa, al 48 | acceder al recurso se debería obtener un error 404. 49 | 50 | La configuración de ejemplo de la API solo configura el recurso 51 | /provincias.json. El resto quedan sin configurar.""" 52 | resp = self.app.get('/api/departamentos.json') 53 | self.assertTrue(resp.status_code == 404) 54 | -------------------------------------------------------------------------------- /tests/test_search_addresses_bulk.py: -------------------------------------------------------------------------------- 1 | import random 2 | from . import GeorefLiveTest 3 | 4 | STREET_NAMES = [ 5 | 'salta', 'santa fe', 'corrientes', 'cordoba', 'mitre', 'calle', 'avenida', 6 | 'sarmiento', '9 de julio', '25 de mayo', 'belgrano', 'buenos aires', 7 | 'san martin', 'general paz' 8 | ] 9 | 10 | 11 | def random_address(): 12 | """Genera una dirección al azar, de cualquier tipo.""" 13 | street_count = random.randint(1, 3) 14 | 15 | if random.getrandbits(1): 16 | door_num = ' {}'.format(random.randint(100, 3000)) 17 | else: 18 | door_num = '' 19 | 20 | if street_count == 1: 21 | return '{}{}'.format(random.choice(STREET_NAMES), door_num) 22 | 23 | if street_count == 2: 24 | return '{}{} esquina {}'.format(random.choice(STREET_NAMES), 25 | door_num, 26 | random.choice(STREET_NAMES)) 27 | 28 | return '{}{} e/ {} y {}'.format(random.choice(STREET_NAMES), 29 | door_num, 30 | random.choice(STREET_NAMES), 31 | random.choice(STREET_NAMES)) 32 | 33 | 34 | class SearchAddressesBulkTest(GeorefLiveTest): 35 | """Pruebas de búsqueda de direcciones por lote, para cualquier tipo.""" 36 | 37 | def setUp(self): 38 | self.endpoint = '/api/direcciones' 39 | self.entity = 'direcciones' 40 | super().setUp() 41 | 42 | def test_short_bulk_1(self): 43 | """Una búsqueda de N direcciones utilizando POST debería ser 44 | equivalente a buscar las N direcciones por separado.""" 45 | self.assert_bulk_results(1) 46 | 47 | def test_short_bulk_50(self): 48 | """Una búsqueda de N direcciones utilizando POST debería ser 49 | equivalente a buscar las N direcciones por separado.""" 50 | self.assert_bulk_results(25) 51 | 52 | def assert_bulk_results(self, size): 53 | queries = [] 54 | for _ in range(size): 55 | queries.append({ 56 | 'direccion': random_address(), 57 | 'max': 1, 58 | 'campos': random.choice(['basico', 'estandar', 'completo']) 59 | }) 60 | 61 | individual_results = [] 62 | for query in queries: 63 | individual_results.append(self.get_response(params=query, 64 | return_value='full')) 65 | 66 | bulk_results = self.get_response(method='POST', body={ 67 | 'direcciones': queries 68 | }) 69 | 70 | self.assertEqual(individual_results, bulk_results) 71 | --------------------------------------------------------------------------------