├── test └── api-test │ ├── population_test.js │ ├── controllers-test │ ├── README.md │ ├── hello_world.js │ └── general_test.js │ └── api_test.js ├── .babelrc ├── .deployment ├── api ├── helpers │ ├── README.md │ ├── notify.js │ ├── logger.js │ ├── volos.js │ ├── postgres.js │ ├── auth0_rules.js │ ├── auth.js │ └── general.js ├── controllers │ ├── README.md │ └── general.js └── mocks │ └── README.md ├── index.js ├── .gitignore ├── config ├── README.md └── default.yaml ├── .eslintignore ├── .codeclimate.yml ├── public ├── images │ ├── afg_11_2.png │ ├── dl_gadm.png │ ├── epi_iso.png │ ├── afg_admin_1.png │ ├── case_output.png │ ├── countries.png │ ├── expand_pop.gif │ ├── expand_pop.png │ ├── pop_mos_epi.png │ ├── admin_levels.png │ ├── afg_shapefile.png │ ├── afg_thru_zwe.png │ ├── mos_endpoints.png │ ├── pop_mos_epi_2.png │ ├── rsz_countries.png │ ├── admin_1_and_2v2.png │ ├── case_endpoints.png │ ├── rsz_2countries.png │ ├── admin_id_explain_all.png │ ├── admin_levels_arrows.png │ ├── rsz_afghanistan_population.png │ └── Screen Shot 2017-10-26 at 1.42.14 PM.png └── aggregations │ ├── cases │ └── zika │ │ └── paho │ │ └── epi │ │ └── 2016-11-17.json │ ├── population │ └── worldpop │ │ └── gadm2-8 │ │ └── afg_2_gadm2-8^popmap15adj^worldpop^42348516^248596^641869.188.json │ └── mosquito │ └── aegypti │ └── simon_hay │ └── gadm2-8 │ └── afg_2_gadm2-8^aegypti^simon_hay^0.12469^376427.json ├── .travis.yml ├── .github └── CONTRIBUTING.md ├── get_population_from_worldbank.js ├── LICENSE.txt ├── run-tests.sh ├── .eslintrc.js ├── scripts └── tallymobility.sh ├── utils ├── azure.js └── data_access.js ├── config-sample.js ├── package.json ├── server.js ├── deploy.sh ├── README.md └── POP.csv /test/api-test/population_test.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { "presets": ["es2015"] } 2 | -------------------------------------------------------------------------------- /.deployment: -------------------------------------------------------------------------------- 1 | [config] 2 | command = bash deploy.sh -------------------------------------------------------------------------------- /api/helpers/README.md: -------------------------------------------------------------------------------- 1 | Place helper files in this directory. 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | require('./server') 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | config.js 4 | build 5 | -------------------------------------------------------------------------------- /config/README.md: -------------------------------------------------------------------------------- 1 | Place configuration files in this directory. 2 | -------------------------------------------------------------------------------- /api/controllers/README.md: -------------------------------------------------------------------------------- 1 | Place your controllers in this directory. 2 | -------------------------------------------------------------------------------- /api/mocks/README.md: -------------------------------------------------------------------------------- 1 | Place controllers for mock mode in this directory. 2 | -------------------------------------------------------------------------------- /test/api-test/controllers-test/README.md: -------------------------------------------------------------------------------- 1 | Place your controller tests in this directory. 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | volos.js 2 | hello_world.js 3 | auth0_rules.js 4 | config.js 5 | config-sample.js 6 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | eslint: 3 | enabled: true 4 | nodesecurity: 5 | enabled: true 6 | -------------------------------------------------------------------------------- /public/images/afg_11_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/afg_11_2.png -------------------------------------------------------------------------------- /public/images/dl_gadm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/dl_gadm.png -------------------------------------------------------------------------------- /public/images/epi_iso.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/epi_iso.png -------------------------------------------------------------------------------- /public/images/afg_admin_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/afg_admin_1.png -------------------------------------------------------------------------------- /public/images/case_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/case_output.png -------------------------------------------------------------------------------- /public/images/countries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/countries.png -------------------------------------------------------------------------------- /public/images/expand_pop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/expand_pop.gif -------------------------------------------------------------------------------- /public/images/expand_pop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/expand_pop.png -------------------------------------------------------------------------------- /public/images/pop_mos_epi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/pop_mos_epi.png -------------------------------------------------------------------------------- /public/images/admin_levels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/admin_levels.png -------------------------------------------------------------------------------- /public/images/afg_shapefile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/afg_shapefile.png -------------------------------------------------------------------------------- /public/images/afg_thru_zwe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/afg_thru_zwe.png -------------------------------------------------------------------------------- /public/images/mos_endpoints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/mos_endpoints.png -------------------------------------------------------------------------------- /public/images/pop_mos_epi_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/pop_mos_epi_2.png -------------------------------------------------------------------------------- /public/images/rsz_countries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/rsz_countries.png -------------------------------------------------------------------------------- /public/images/admin_1_and_2v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/admin_1_and_2v2.png -------------------------------------------------------------------------------- /public/images/case_endpoints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/case_endpoints.png -------------------------------------------------------------------------------- /public/images/rsz_2countries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/rsz_2countries.png -------------------------------------------------------------------------------- /public/images/admin_id_explain_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/admin_id_explain_all.png -------------------------------------------------------------------------------- /public/images/admin_levels_arrows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/admin_levels_arrows.png -------------------------------------------------------------------------------- /public/images/rsz_afghanistan_population.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/rsz_afghanistan_population.png -------------------------------------------------------------------------------- /public/images/Screen Shot 2017-10-26 at 1.42.14 PM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/magicbox-open-api/HEAD/public/images/Screen Shot 2017-10-26 at 1.42.14 PM.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | env: 2 | global: 3 | - CC_TEST_REPORTER_ID=a8ed8045541a5b273886dcf01e36c2e2d1d6b9a03dcf1b7322162d07b7247909 4 | 5 | language: node_js 6 | node_js: 7 | - "8" 8 | cache: 9 | directories: 10 | - "node_modules" 11 | 12 | before_script: 13 | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 14 | - chmod +x ./cc-test-reporter 15 | - ./cc-test-reporter before-build 16 | after_script: 17 | - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT 18 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing guidelines 2 | ======================= 3 | 4 | See the [MagicBox contributing 5 | guidelines](https://github.com/unicef/magicbox/blob/master/.github/CONTRIBUTING.md) 6 | first for general guidelines that apply to _all_ MagicBox projects! 7 | 8 | 9 | ## Checklist for magicbox-open-api 10 | 11 | Before submitting a new pull request to this repo, follow these steps: 12 | 13 | 1. Follow [MagicBox contributing 14 | guidelines](https://github.com/unicef/magicbox/blob/master/.github/CONTRIBUTING.md) 15 | 2. Run all unit tests (`npm test`) 16 | -------------------------------------------------------------------------------- /get_population_from_worldbank.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const csvtojson = require('csvtojson') 3 | let population = {} 4 | const source = 'worldbank' 5 | const admin_level = 0 6 | 7 | csvtojson() 8 | .fromFile('./POP.csv') 9 | .on('json', row => { 10 | population[row.iso.toLowerCase()] = { 11 | country: row.iso.toLowerCase(), 12 | data_source: source, 13 | admin_level: admin_level, 14 | sum: parseInt(row.population.replace(/,/g, '')) * 1000 15 | } 16 | }) 17 | .on('done', () => { 18 | fs.writeFileSync('./population.json', JSON.stringify(population)) 19 | }) 20 | 21 | // ../mnt/population/worldbank/ 22 | -------------------------------------------------------------------------------- /api/helpers/notify.js: -------------------------------------------------------------------------------- 1 | import config from '../../config' 2 | let nodemailer = require('nodemailer'); 3 | 4 | let transporter = nodemailer.createTransport({ 5 | service: 'gmail', 6 | auth: { 7 | user: config.email.user, 8 | pass: config.email.password 9 | } 10 | }); 11 | 12 | /** 13 | * Returns ip, path and query of the request 14 | * @param {object} subject request object 15 | * @param {object} message request object 16 | * @return {object} object with ip, path and query of the request 17 | */ 18 | module.exports = (subject, message) => { 19 | return new Promise((resolve, reject)=> { 20 | let mailOptions = { 21 | from: config.email.from, 22 | to: config.email.to, 23 | subject: subject, 24 | text: JSON.stringify(message) 25 | }; 26 | transporter.sendMail(mailOptions, function(error, info) { 27 | if (error) { 28 | console.log(error); 29 | return reject(error) 30 | } else { 31 | console.log('Email sent: ' + info.response); 32 | return resolve(error) 33 | } 34 | }); 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /config/default.yaml: -------------------------------------------------------------------------------- 1 | # swagger configuration file 2 | 3 | # values in the swagger hash are system configuration for swagger-node 4 | swagger: 5 | 6 | fittingsDirs: [ api/fittings ] 7 | defaultPipe: null 8 | swaggerControllerPipe: swagger_controllers # defines the standard processing pipe for controllers 9 | 10 | # values defined in the bagpipes key are the bagpipes pipes and fittings definitions 11 | # (see https://github.com/apigee-127/bagpipes) 12 | bagpipes: 13 | 14 | _router: 15 | name: swagger_router 16 | mockMode: false 17 | mockControllersDirs: [ api/mocks ] 18 | controllersDirs: [ api/controllers ] 19 | 20 | _swagger_validate: 21 | name: swagger_validator 22 | validateResponse: false 23 | 24 | # pipe for all swagger-node controllers 25 | swagger_controllers: 26 | - onError: json_error_handler 27 | - cors 28 | - swagger_security 29 | - _swagger_validate 30 | - express_compatibility 31 | - _router 32 | 33 | # pipe to serve swagger (endpoint is in swagger.yaml) 34 | swagger_raw: 35 | name: swagger_raw 36 | 37 | # any other values in this file are just loaded into the config for application access... 38 | -------------------------------------------------------------------------------- /test/api-test/controllers-test/hello_world.js: -------------------------------------------------------------------------------- 1 | // var should = require('should'); 2 | // var request = require('supertest'); 3 | // var server = require('../../../app'); 4 | // 5 | // describe('controllers', function() { 6 | // 7 | // describe('hello_world', function() { 8 | // 9 | // describe('GET /hello', function() { 10 | // 11 | // it('should return a default string', function(done) { 12 | // 13 | // request(server) 14 | // .get('/hello') 15 | // .set('Accept', 'application/json') 16 | // .expect('Content-Type', /json/) 17 | // .expect(200) 18 | // .end(function(err, res) { 19 | // should.not.exist(err); 20 | // 21 | // res.body.should.eql('Hello, stranger!'); 22 | // 23 | // done(); 24 | // }); 25 | // }); 26 | // 27 | // it('should accept a name parameter', function(done) { 28 | // 29 | // request(server) 30 | // .get('/hello') 31 | // .query({ name: 'Scott'}) 32 | // .set('Accept', 'application/json') 33 | // .expect('Content-Type', /json/) 34 | // .expect(200) 35 | // .end(function(err, res) { 36 | // should.not.exist(err); 37 | // 38 | // res.body.should.eql('Hello, Scott!'); 39 | // 40 | // done(); 41 | // }); 42 | // }); 43 | // 44 | // }); 45 | // 46 | // }); 47 | // 48 | // }); 49 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, UNICEF 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /test/api-test/controllers-test/general_test.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai' 2 | import * as generalController from '../../../api/controllers/general.js' 3 | import * as general_helper from '../../../api/helpers/general.js' 4 | import sinon from 'sinon' 5 | import httpMocks from 'node-mocks-http' 6 | 7 | process.env.NODE_ENV = 'test' 8 | 9 | 10 | const request = httpMocks.createRequest({ 11 | method: 'GET', 12 | swagger: { 13 | params: { 14 | dummyParam1: {value: 'dummyValue1'}, 15 | dummyParam2: {value: 'dummyValue2'} 16 | }, 17 | apiPath: 'api/population/countries' 18 | } 19 | }) 20 | 21 | 22 | const response = httpMocks.createResponse() 23 | 24 | describe('testing general controller', () => { 25 | it('testing getParams', (done) => { 26 | const params = generalController.getParams(request) 27 | Object.keys(params).forEach(key => { 28 | chai.expect(request.swagger.params).to.have.property(key) 29 | chai.expect(request.swagger.params[key].value).to.equal(params[key]) 30 | }) 31 | done() 32 | }) 33 | 34 | it('testing getProperties', (done) => { 35 | let fakeReturn = { 36 | key: 'population_worldpop', 37 | properties: 38 | ['afg'] 39 | } 40 | 41 | sinon.stub(general_helper, 'getProperties').resolves(fakeReturn) 42 | sinon.stub(response, 'json').returns(fakeReturn) 43 | generalController.getProperties(request, response) 44 | .then(result => { 45 | chai.expect(result.key).to.equal(fakeReturn.key) 46 | chai.expect(result.properties).to.equal(fakeReturn.properties) 47 | done() 48 | }) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Script to run unit tests on code. Unit tests are created in Mocha: 4 | # https://mochajs.org/ 5 | # 6 | 7 | 8 | ##### INSTALLATION ###### 9 | 10 | # Set env variables for eslint and mocha 11 | cd "$(git rev-parse --show-toplevel)" 12 | ESLINT="node_modules/.bin/eslint" 13 | MOCHA="node_modules/.bin/mocha" 14 | pwd 15 | 16 | # Ensure eslint is installed 17 | if [[ ! -x "$ESLINT" ]]; then 18 | printf "\t\033[41mPlease install ESlint\033[0m (npm install eslint)\n" 19 | exit 1 20 | fi 21 | 22 | # Review staged files 23 | STAGED_FILES=($(git diff --cached --name-only --diff-filter=ACM | grep ".jsx\{0,1\}$")) 24 | 25 | 26 | ##### MOCHA / JS UNIT TESTS ##### 27 | 28 | # Counters for failed tests and lints 29 | testFailed=0 30 | lintFailed=0 31 | 32 | # Run Mocha tests 33 | $MOCHA --compilers js:babel-core/register test --recursive 34 | if [[ $? != 0 ]] ; then 35 | testFailed=1 36 | fi 37 | 38 | if [[ $testFailed == 0 ]]; then 39 | printf "\n\033[42mAll test passed\033[0m\n" 40 | printf "\n\033[42mCOMMIT SUCCEEDED\033[0m\n" 41 | exit 0 42 | else 43 | printf "\n\033[41mCOMMIT FAILED:\033[0m Fix tests and try again\n" 44 | exit 1 45 | fi 46 | 47 | # Run linting 48 | echo "ESLint'ing ${#STAGED_FILES[@]} files" 49 | 50 | if [[ "$STAGED_FILES" = "" ]]; then 51 | exit 0 52 | fi 53 | 54 | for file in ${STAGED_FILES}; do 55 | git show :$file | $ESLINT --stdin --stdin-filename "$file" 56 | if [[ $? != 0 ]] ; then 57 | lintFailed=1 58 | fi 59 | done; 60 | 61 | if [[ $lintFailed == 0 ]]; then 62 | printf "\n\033[42mJsLint Successfull\033[0m\n" 63 | else 64 | printf "\n\033[41mCOMMIT FAILED:\033[0m Fix eslint errors and try again\n" 65 | exit 1 66 | fi 67 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "google", 3 | "parserOptions": { 4 | "ecmaVersion": 7, 5 | "sourceType": "module", 6 | "ecmaFeatures": { 7 | "jsx": true, 8 | } 9 | }, 10 | env: { 11 | 'es6': true, 12 | 'jasmine': true 13 | }, 14 | rules: { 15 | "new-cap": [2, { 16 | "capIsNewExceptions": ["SwaggerUi"] 17 | }], 18 | "switch-colon-spacing": 0, 19 | // need to keep as it's known issue with a dependency of airbnb standards 20 | "arrow-parens": 0, 21 | 'jsx-a11y/href-no-hash': 0, 22 | 'camelcase': 0, // allowing underscore case 23 | 'semi': 0, // allowing lines with or without semi colon at the end 24 | 'arrow-body-style': ['error', 'always'], // because we return BIG BIG promises 25 | 'no-shadow': ['error', { 'builtinGlobals': true, 'hoist': 'all' }], 26 | 'no-param-reassign': 0, 27 | 'no-unused-vars': 'error', 28 | 'no-use-before-define': 0, 29 | 'radix': ['error', 'as-needed'], 30 | 'max-len': ['error', { 'ignoreComments': true, 'ignoreTemplateLiterals': true, 'ignoreUrls': true }], 31 | 'require-jsdoc': ['error', { 32 | 'require': { 33 | 'FunctionDeclaration': true, 34 | 'MethodDefinition': true, 35 | 'ClassDeclaration': true, 36 | 'ArrowFunctionExpression': true 37 | } 38 | }], 39 | 'valid-jsdoc': 2, 40 | 'comma-dangle': ['error', 'never'], 41 | 42 | 'one-var': 0, 43 | 'one-var-declaration-per-line': 0, 44 | // disable linebreak-style check so that ESLint won't give WinOS trouble 45 | 'linebreak-style': 0, 46 | 'global-require': 0 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /scripts/tallymobility.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Mobility data have the following attributes: 5 | # id_origin,id_destination,journeys,people 6 | # 7 | # This script searches in a given directory for all the files matching the 8 | # pattern YYYY-MM-DD.csv (mobility data), read the file contents and append 9 | # the total number of journeys and people to the name of the file. 10 | # 11 | # YYYY-MM-DD.csv -> YYYY-MM-DD^JOURNEYS-PEOPLE.csv 12 | # 13 | 14 | # color codes 15 | orange=202 16 | red=160 17 | blue=33 18 | green=34 19 | 20 | # color echo message 21 | function colored_echo { 22 | case $# in 23 | # just the message 24 | 1) msg=$1 25 | ;; 26 | # message with foreground color 27 | 2) msg="\\033[38;5;${2}m${1}\\033[0m" 28 | ;; 29 | # message with foreground and background color 30 | *) msg="\\033[48;5;${3};38;5;${2}m${1}\\033[0m" 31 | ;; 32 | esac 33 | 34 | echo -e "$msg" 35 | } 36 | 37 | # verify input 38 | if [ $# -ne 1 ]; 39 | then 40 | colored_echo "Use: $0 " $red 41 | exit 1 42 | fi 43 | 44 | # directory where are the files 45 | directory="$1" 46 | # file pattern to match 47 | file_pattern="^([0-9]{4}-[0-9]{2}-[0-9]{2})\.csv$" 48 | # old working directory 49 | old_wd=`pwd` 50 | 51 | colored_echo "Searching files in $1" $green 52 | 53 | # enter in directory 54 | cd $directory 55 | 56 | # get all .csv files in current directory 57 | for file in `ls *.csv`; do 58 | # if the current file matches our pattern 59 | if [[ $file =~ $file_pattern ]]; 60 | then 61 | # process it 62 | sum=(`awk -F, '{ journeys_sum += $3; people_sum += $4 } END { print journeys_sum, people_sum }' $file`) 63 | # new file name 64 | new_file="${BASH_REMATCH[1]}^${sum[0]}-${sum[1]}.csv" 65 | # rename file 66 | mv $file $new_file 67 | # show processed information 68 | colored_echo "old file: ${file}, journeys: ${sum[0]}, people: ${sum[1]}, new file: ${new_file}" $green 69 | else 70 | # ignore it 71 | colored_echo "Ignoring: $file" $orange 72 | fi 73 | done 74 | 75 | # return to previous working directory 76 | cd $old_wd 77 | 78 | colored_echo "Done!" $green 79 | 80 | exit 0 81 | 82 | -------------------------------------------------------------------------------- /api/helpers/logger.js: -------------------------------------------------------------------------------- 1 | import Mixpanel from 'mixpanel' 2 | import dataFormatter from 'dateformat' 3 | import config from './../../config' 4 | const mixpanel = Mixpanel.init(config.logger.key); 5 | 6 | /** 7 | * Logs client-ip, requested url, query params for incoming request. 8 | * @param {Object} request request object 9 | * @param {Object} response response object 10 | * @param {Function} next next handler to call 11 | */ 12 | export const logRequest = (request, response, next) => { 13 | if (request.url.indexOf('/api/v1/') !== -1) { 14 | let logObject = getRequestParams(request) 15 | log('REQUEST', logObject) 16 | } 17 | next() 18 | } 19 | 20 | /** 21 | * Log given information as INFO event 22 | * @param {Object} logObject Object to log 23 | */ 24 | export const info = (logObject) => { 25 | log('INFO', logObject) 26 | } 27 | 28 | /** 29 | * Log given information as WARN event 30 | * @param {Object} logObject Object to log 31 | */ 32 | export const warn = (logObject) => { 33 | log('WARN', logObject) 34 | } 35 | 36 | /** 37 | * Log given information as ERROR event 38 | * @param {Object} logObject Object to log 39 | */ 40 | export const error = (logObject) => { 41 | log('ERROR', logObject) 42 | } 43 | 44 | 45 | /** 46 | * Log given event and specified object 47 | * @param {string} eventName name of the event 48 | * @param {Object} logObject Object to log 49 | */ 50 | export const log = (eventName, logObject) => { 51 | logObject.timestamp = dataFormatter(Date.now(), 'isoDateTime') 52 | mixpanel.track(eventName, logObject) 53 | } 54 | 55 | /** 56 | * Logs error for the request 57 | * @param {object} request request object 58 | * @param {object} err error object 59 | */ 60 | export const logErrorResponse = (request, err) => { 61 | let logObject = getRequestParams(request) 62 | logObject.desc = err 63 | error(logObject) 64 | } 65 | 66 | /** 67 | * Returns ip, path and query of the request 68 | * @param {object} request request object 69 | * @return {object} object with ip, path and query of the request 70 | */ 71 | export const getRequestParams = (request) => { 72 | return { 73 | clientIp: request.clientIp, 74 | path: request.url.replace('/api/v1/', ''), 75 | query: request.param 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /utils/azure.js: -------------------------------------------------------------------------------- 1 | import config from '../config' 2 | const base_dir = config.azure.directory 3 | const csvjson = require('csvjson'); 4 | // import jsonfile from 'jsonfile' 5 | 6 | /** 7 | * Gets list of country population aggregation blobs 8 | * Just in case we want to only process files that we don't already have 9 | * @param{String} fileSvc - Azure file service - might be null 10 | * @param{String} kind - Name of blob container 11 | * @param{String} dir - Name of blob container 12 | * @return{Promise} Fulfilled list of blobs 13 | */ 14 | export function get_file_list(fileSvc, kind, dir) { 15 | let path = config[kind].path 16 | path = dir ? path + dir : path 17 | return new Promise((resolve, reject) => { 18 | fileSvc.listFilesAndDirectoriesSegmented( 19 | base_dir, path, null, function(err, result, response) { 20 | if (err) { 21 | return reject(err); 22 | } else { 23 | console.log(base_dir, path, '!!!!', JSON.stringify(result)) 24 | resolve(result); 25 | } 26 | }); 27 | }); 28 | }; 29 | 30 | /** 31 | * Read a file and return Json object with the file content 32 | * @param{String} fileSvc - Azure file service - might be null 33 | * @param {String} key Key for the request. This determines root dir for the file 34 | * @param {String} dir dir of file 35 | * @param {String} fileName File to read 36 | * @return {Promise} Fulfilled when records are returned 37 | */ 38 | export function read_file(fileSvc, key, dir, fileName) { 39 | return new Promise((resolve, reject) => { 40 | let path = config[key].path; 41 | path = dir ? path + dir : path; 42 | // Check if using azure, if not, grab from prepped data in public dir. 43 | fileSvc.getFileToText( 44 | base_dir, path, fileName, (error, fileContent, file) => { 45 | if (!error) { 46 | // uncomment to create fixtures. 47 | // jsonfile.writeFile('./public/aggregations/' + [key, dir, fileName].join('/'), JSON.parse(fileContent), function (err) { 48 | // console.error(err) 49 | // }) 50 | resolve(csvjson.toObject(fileContent, {})) 51 | // resolve(JSON.parse(fileContent)); 52 | } else { 53 | console.log('Error while reading', base_dir+path+fileName); 54 | } 55 | }); 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /config-sample.js: -------------------------------------------------------------------------------- 1 | `` 2 | module.exports = { 3 | azure: { 4 | storage_account: process.env.STORAGE_ACCOUNT_NAME || 'storage_account_name', 5 | key1: process.env.KEY1 || 'azure_storage_key', 6 | directory: 'aggregations' 7 | }, 8 | auth0: { 9 | auth_domain: process.env.AUTH0_DOMAIN || 'AUTH0_DOMAIN', 10 | client_id: process.env.AUTH0_CLIENTID || 'AUTH0_CLIENTID', 11 | callback_url: 'http://localhost:8000/api/v1/token/', 12 | client_secret: process.env.AUTH0_CLIENT_SECRET || 'CLIENT_SECRET', 13 | auth_url: process.env.AUTH_URL || 'AUTH_URL', 14 | // callback_url: 'http://magicbox-open-api.azurewebsites.net/api/v1/token/' 15 | callback_url: process.env.AUTH0_CB_URL || 'http://localhost:8000/api/v1/token/', 16 | redirect_uri: process.env.REDIRECT_URI || 'http://localhost:8000/', 17 | roles: { 18 | 'unicef.org': 'admin', 19 | 'projectconnect.world': 'proco', 20 | }, 21 | }, 22 | mobility: { 23 | path: 'mobility/', 24 | default_country: 'colombia', 25 | default_database: 'santiblanko', 26 | default_source: 'telefonica', 27 | default_admin_level: 2 28 | }, 29 | mosquito: { 30 | val_type: 'mean', 31 | source: 'The global distribution of the arbovirus vectors Aedes aegypti and Ae. albopictus', 32 | source_url: 'https://elifesciences.org/content/4/e08347', 33 | path: 'mosquito/', 34 | default_source: 'simon_hay', 35 | default_database: 'gadm2-8' 36 | }, 37 | population: { 38 | val_type: 'sum', 39 | source: 'worldpop.org.uk', 40 | source_url: 'http://www.worldpop.org.uk', 41 | path: 'population/', 42 | default_source: 'worldpop', 43 | default_database: 'gadm2-8' 44 | }, 45 | 'geo-properties': { 46 | path: '../geo-properties/' 47 | }, 48 | cases: { 49 | path: 'cases/', 50 | zika: { 51 | source: 'Paho', 52 | source_url: 'http://www.paho.org/hq/index.php?option=com_content&view=article&id=12390&Itemid=42090&lang=en', 53 | path: 'zika/paho/' 54 | } 55 | }, 56 | logger: { 57 | key: 'mixpanel_token' 58 | }, 59 | // Optional custom database config options for PostgreSQL 60 | db: { 61 | user: process.env.DB_USER, 62 | host: process.env.DB_HOST, 63 | database: process.env.DB_NAME, 64 | password: process.env.DB_PASSWORD, 65 | port: process.env.DB_PORT 66 | }, 67 | max_query_result: 100000000 68 | }; 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "magicbox8", 3 | "engines": { 4 | "node": "8.0" 5 | }, 6 | "version": "0.0.1", 7 | "private": true, 8 | "description": "New Swagger API Project", 9 | "keywords": [], 10 | "author": "", 11 | "license": "", 12 | "main": "./server.js", 13 | "dependencies": { 14 | "a127-magic": "^0.12.1", 15 | "auth0": "^2.24.0", 16 | "auth0-js": "^9.13.0", 17 | "azure-storage": "^2.10.3", 18 | "babel-cli": "^6.24.1", 19 | "babel-eslint": "^10.1.0", 20 | "babel-preset-es2015": "^6.24.1", 21 | "babel-preset-node6": "^11.0.0", 22 | "babel-register": "^6.24.1", 23 | "babel-watch": "^7.0.0", 24 | "bluebird": "^3.7.2", 25 | "compression": "^1.7.4", 26 | "csvjson": "^5.1.0", 27 | "csvtojson": "^2.0.10", 28 | "dateformat": "^2.0.0", 29 | "deepcopy": "^0.6.3", 30 | "express": "^4.17.1", 31 | "geojson": "^0.5.0", 32 | "i18n-iso-countries": "^3.7.8", 33 | "is-json": "^2.0.1", 34 | "jsonfile": "^4.0.0", 35 | "mixpanel": "^0.10.2", 36 | "moment": "^2.24.0", 37 | "pg": "^7.18.2", 38 | "pg-cursor": "^1.3.0", 39 | "qs": "^6.9.3", 40 | "react": "^15.5.4", 41 | "react-dom": "^15.5.4", 42 | "request-ip": "^2.1.3", 43 | "swagger-express-mw": "^0.7.0", 44 | "swagger-tools": "^0.10.4", 45 | "volos-cache-memory": "^0.10.1", 46 | "volos-swagger": "^0.8.0" 47 | }, 48 | "scripts": { 49 | "start": "node build/server.js", 50 | "clean": "rm -rf build && mkdir build", 51 | "build-server": "babel -d build ./ --ignore node_modules,build --copy-files", 52 | "build": "npm run clean && npm run build-server", 53 | "prod": "babel-node server.js", 54 | "dev": "babel-watch server.js", 55 | "precommit": "./run-tests.sh", 56 | "pretest": "if [ ! -f ./config.js ]; then cp config-sample.js config.js; fi", 57 | "test": "./run-tests.sh", 58 | "tallymobility": "scripts/tallymobility.sh public/aggregations/mobility/acme/santiblanko/col" 59 | }, 60 | "devDependencies": { 61 | "chai": "^4.2.0", 62 | "chai-http": "^4.3.0", 63 | "eslint": "^6.8.0", 64 | "eslint-config-google": "^0.9.1", 65 | "eslint-plugin-babel": "^4.1.2", 66 | "husky": "^0.13.4", 67 | "mocha": "^6.2.3", 68 | "node-mocks-http": "^1.8.1", 69 | "serve": "^11.3.0", 70 | "should": "^7.1.0", 71 | "sinon": "^4.4.4", 72 | "supertest": "^4.0.2" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | import SwaggerExpress from 'swagger-express-mw' 2 | import SwaggerUi from 'swagger-tools/middleware/swagger-ui' 3 | import volosCache from 'volos-cache-memory' 4 | import compression from 'compression' 5 | import express from 'express' 6 | import deepcopy from 'deepcopy' 7 | import * as auth from './api/helpers/auth' 8 | const VOLOS_RESOURCE = 'x-volos-resources' 9 | import requestIp from 'request-ip' 10 | import * as logger from './api/helpers/logger' 11 | 12 | const config = { 13 | appRoot: __dirname, 14 | port: process.env.PORT || 8000, 15 | swaggerSecurityHandlers: {Bearer: auth.verifyToken} 16 | } 17 | console.log(process.version); 18 | const app = express() 19 | 20 | app.use(compression()) 21 | app.use(requestIp.mw()) 22 | app.use(logger.logRequest) 23 | 24 | 25 | SwaggerExpress.create(config, (err, swaggerExpress) => { 26 | if (err) { 27 | throw err 28 | } 29 | 30 | let cacheOptions = swaggerExpress.runner.swagger[VOLOS_RESOURCE].cache.options 31 | let cacheName = swaggerExpress.runner.swagger[VOLOS_RESOURCE].cache.name 32 | let cache = volosCache.create(cacheName, cacheOptions) 33 | cacheOptions.key = getCacheKey 34 | 35 | // eliminate path that has x-hide property 36 | let swaggerObject = deepcopy(swaggerExpress.runner.swagger) 37 | let new_paths = Object.keys(swaggerObject.paths).reduce((obj, path) => { 38 | if (!swaggerObject.paths[path]['x-hide']) { 39 | obj[path] = swaggerObject.paths[path] 40 | } 41 | return obj 42 | }, {}) 43 | 44 | swaggerObject.paths = new_paths 45 | 46 | app.use(SwaggerUi(swaggerObject)) 47 | 48 | // Serve the Swagger documents and Swagger UI 49 | // app.use(swaggerExpress.runner.swaggerTools.swaggerUi()); 50 | // install middleware 51 | app.use(swaggerExpress.runner.swaggerTools.swaggerMetadata()) 52 | app.use(swaggerExpress.runner.swaggerTools.swaggerSecurity(config.swaggerSecurityHandlers)) 53 | 54 | app.use(cache.expressMiddleware().cache({key: getCacheKey})) 55 | // Provide the security handlers 56 | 57 | swaggerExpress.register(app) 58 | 59 | app.listen(config.port, () => { 60 | console.log(`Open maps API is up on http://localhost:${config.port}`) 61 | }) 62 | }) 63 | 64 | /** 65 | * Extracts and returns data from shapefiles 66 | * @param {object} req request object 67 | * @return {sting} cacheKey 68 | */ 69 | const getCacheKey = (req) => { 70 | let cacheKey = null 71 | let url = req.originalUrl 72 | 73 | if (url.indexOf('/api/v1/') !== -1) { 74 | if (url.substring(url.length - 1) === '/') { 75 | url = url.substring(0, url.length - 1) 76 | } 77 | let urlParts = url.split('/') 78 | cacheKey = urlParts[3] 79 | 80 | urlParts.slice(4, urlParts.length).forEach(part => { 81 | cacheKey += '_' + part 82 | }) 83 | } 84 | 85 | return cacheKey 86 | } 87 | 88 | 89 | export default app 90 | -------------------------------------------------------------------------------- /api/helpers/volos.js: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | The MIT License (MIT) 3 | Copyright (c) 2014 Apigee Corporation 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 18 | THE SOFTWARE. 19 | ****************************************************************************/ 20 | 'use strict'; 21 | 22 | var debug = require('debug')('helpers'); 23 | const config = require('../../config') 24 | 25 | module.exports = { 26 | cachePopulation, 27 | cachePopulationProperties, 28 | cachePopulationCountries, 29 | cacheCases, 30 | cacheCasesProperties, 31 | cacheMosquitoProperties, 32 | cacheMosquito 33 | }; 34 | 35 | function cachePopulation(req) { 36 | let key = 'population' + getKeyForRequest(req.swagger.params) 37 | if (debug.enabled) { debug('Cache Key: '+key); } 38 | return key; 39 | } 40 | 41 | function cachePopulationCountries(req) { 42 | let key = 'population_' + config.population.default_source + getKeyForRequest(req.swagger.params) 43 | if (debug.enabled) { debug('Cache Key: '+ key + '_properties'); } 44 | return key; 45 | } 46 | 47 | function cacheMosquitoProperties(req) { 48 | let key = 'mosquito' 49 | key += getKeyForRequest(req.swagger.params) 50 | if (debug.enabled) { debug('Cache Key: '+ key + '_properties'); } 51 | console.log('key', key); 52 | return key; 53 | } 54 | 55 | function cacheMosquito(req) { 56 | let key = 'mosquito' + getKeyForRequest(req.swagger.params) 57 | if (debug.enabled) { debug('Cache Key: '+key); } 58 | return key; 59 | } 60 | 61 | function cacheCases(req) { 62 | var key = 'cases' + getKeyForRequest(req.swagger.params); 63 | if (debug.enabled) { debug('Cache Key: '+ key); } 64 | return key; 65 | } 66 | 67 | function cachePopulationProperties(req) { 68 | let key = 'population' 69 | key += getKeyForRequest(req.swagger.params) 70 | if (debug.enabled) { debug('Cache Key: '+ key + '_properties'); } 71 | return key; 72 | } 73 | 74 | function cacheCasesProperties(req) { 75 | let key = 'cases' 76 | key += getKeyForRequest(req.swagger.params) 77 | if (debug.enabled) { debug('Cache Key: '+ key + '_properties'); } 78 | return key; 79 | } 80 | 81 | const getKeyForRequest = (params) => { 82 | let key = '' 83 | let paramKeys = Object.keys(params) 84 | for (let property of paramKeys) { 85 | if (params[property] === undefined) { 86 | key += '_' + property 87 | break; 88 | } else { 89 | key += '_' + params[property].value 90 | } 91 | } 92 | return key 93 | } 94 | -------------------------------------------------------------------------------- /utils/data_access.js: -------------------------------------------------------------------------------- 1 | import config from '../config' 2 | import azure_storage from 'azure-storage' 3 | import jsonfile from 'jsonfile' 4 | import * as azure_utils from './azure' 5 | import fs from 'fs' 6 | const csvjson = require('csvjson'); 7 | 8 | const storage_account = config.azure.storage_account 9 | const azure_key = (process.env.NODE_ENV !== 'test') ? config.azure.key1 : '' 10 | // Magicbox open api pulls from Azure. 11 | // If you don't have a valid azure storage key 12 | // then data will be served from the public directory 13 | console.log(azure_key) 14 | console.log('____') 15 | const fileSvc = is_valid_key(azure_key) ? 16 | azure_storage.createFileService(storage_account, azure_key) : 17 | null 18 | const base_dir = config.azure.directory 19 | 20 | /** 21 | * Gets list of country population aggregation blobs 22 | * Just in case we want to only process files that we don't already have 23 | * @param{String} key - Azure file service acess key 24 | * @return{Promise} Fulfilled list of blobs 25 | */ 26 | function is_valid_key(key) { 27 | return key.length > 20 28 | } 29 | 30 | /** 31 | * Gets list of country population aggregation blobs 32 | * Just in case we want to only process files that we don't already have 33 | * @param{String} kind - Name of blob container 34 | * @param{String} dir - Name of blob container 35 | * @return{Promise} Fulfilled list of blobs 36 | */ 37 | export function get_file_list(kind, dir) { 38 | return new Promise((resolve, reject) => { 39 | if (fileSvc) { 40 | azure_utils.get_file_list(fileSvc, kind, dir) 41 | .catch(console.log) 42 | .then(resolve) 43 | } else { 44 | let path = './public/aggregations/' + [kind, dir].join('/') 45 | let dir_contents_obj = {entries: {}}; 46 | 47 | dir_contents_obj.entries.directories = fs.readdirSync(path) 48 | .filter(file => { 49 | return fs.statSync(path+'/'+file).isDirectory(); 50 | }).map(e => { 51 | return {name: e}; 52 | }) 53 | 54 | dir_contents_obj.entries.files = fs.readdirSync(path).filter(file => { 55 | return !fs.statSync(path+'/'+file).isDirectory(); 56 | }).map(e => { 57 | return {name: e}; 58 | }) 59 | 60 | resolve(dir_contents_obj) 61 | // fs.readdir('./public/aggregations/' + [kind, dir].join('/'), (err, data) => { 62 | // console.log(data) 63 | // resolve(data) 64 | // }) 65 | } 66 | }) 67 | }; 68 | 69 | /** 70 | * Read a file and return Json object with the file content 71 | * @param {String} key Key for the request. This determines root dir for the file 72 | * @param {String} dir dir of file 73 | * @param {String} fileName File to read 74 | * @return {Promise} Fulfilled when records are returned 75 | */ 76 | export function read_file(key, dir, fileName) { 77 | return new Promise((resolve, reject) => { 78 | // Check if using azure, if not, grab from prepped data in public dir. 79 | if (fileSvc) { 80 | azure_utils.read_file(fileSvc, key, dir, fileName) 81 | .catch(console.log) 82 | .then(resolve) 83 | } else { 84 | if (fileName.match(/\.csv$/)) { 85 | let path = './public/' + [base_dir, key, dir, fileName].join('/') 86 | return read_csv(path).then(resolve); 87 | } 88 | console.log('./public/' + [base_dir, key, dir, fileName].join('/')) 89 | jsonfile.readFile( 90 | './public/' + [base_dir, key, dir, fileName].join('/'), 91 | (err, data) => { 92 | resolve(data) 93 | } 94 | ) 95 | } 96 | }); 97 | } 98 | 99 | /** 100 | * Read a file and return Json object with the file content 101 | * @param {String} path Path to CSV 102 | * @return {Promise} Fulfilled when records are returned 103 | */ 104 | function read_csv(path) { 105 | return new Promise((resolve, reject) => { 106 | let data = fs.readFileSync(path, {encoding: 'utf8'}); 107 | resolve(csvjson.toObject(data, {})) 108 | }) 109 | } 110 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ---------------------- 4 | # KUDU Deployment Script 5 | # Version: 1.0.15 6 | # ---------------------- 7 | 8 | # Helpers 9 | # ------- 10 | 11 | exitWithMessageOnError () { 12 | if [ ! $? -eq 0 ]; then 13 | echo "An error has occurred during web site deployment." 14 | echo $1 15 | exit 1 16 | fi 17 | } 18 | 19 | # Prerequisites 20 | # ------------- 21 | 22 | # Verify node.js installed 23 | hash node 2>/dev/null 24 | exitWithMessageOnError "Missing node.js executable, please install node.js, if already installed make sure it can be reached from current environment." 25 | 26 | # Setup 27 | # ----- 28 | 29 | SCRIPT_DIR="${BASH_SOURCE[0]%\\*}" 30 | SCRIPT_DIR="${SCRIPT_DIR%/*}" 31 | ARTIFACTS=$SCRIPT_DIR/../artifacts 32 | KUDU_SYNC_CMD=${KUDU_SYNC_CMD//\"} 33 | 34 | if [[ ! -n "$DEPLOYMENT_SOURCE" ]]; then 35 | DEPLOYMENT_SOURCE=$SCRIPT_DIR 36 | fi 37 | 38 | if [[ ! -n "$NEXT_MANIFEST_PATH" ]]; then 39 | NEXT_MANIFEST_PATH=$ARTIFACTS/manifest 40 | 41 | if [[ ! -n "$PREVIOUS_MANIFEST_PATH" ]]; then 42 | PREVIOUS_MANIFEST_PATH=$NEXT_MANIFEST_PATH 43 | fi 44 | fi 45 | 46 | if [[ ! -n "$DEPLOYMENT_TARGET" ]]; then 47 | DEPLOYMENT_TARGET=$ARTIFACTS/wwwroot 48 | else 49 | KUDU_SERVICE=true 50 | fi 51 | 52 | if [[ ! -n "$KUDU_SYNC_CMD" ]]; then 53 | # Install kudu sync 54 | echo Installing Kudu Sync 55 | npm install kudusync -g --silent 56 | exitWithMessageOnError "npm failed" 57 | 58 | if [[ ! -n "$KUDU_SERVICE" ]]; then 59 | # In case we are running locally this is the correct location of kuduSync 60 | KUDU_SYNC_CMD=kuduSync 61 | else 62 | # In case we are running on kudu service this is the correct location of kuduSync 63 | KUDU_SYNC_CMD=$APPDATA/npm/node_modules/kuduSync/bin/kuduSync 64 | fi 65 | fi 66 | 67 | # Node Helpers 68 | # ------------ 69 | 70 | selectNodeVersion () { 71 | if [[ -n "$KUDU_SELECT_NODE_VERSION_CMD" ]]; then 72 | SELECT_NODE_VERSION="$KUDU_SELECT_NODE_VERSION_CMD \"$DEPLOYMENT_SOURCE\" \"$DEPLOYMENT_TARGET\" \"$DEPLOYMENT_TEMP\"" 73 | eval $SELECT_NODE_VERSION 74 | exitWithMessageOnError "select node version failed" 75 | 76 | if [[ -e "$DEPLOYMENT_TEMP/__nodeVersion.tmp" ]]; then 77 | NODE_EXE=`cat "$DEPLOYMENT_TEMP/__nodeVersion.tmp"` 78 | exitWithMessageOnError "getting node version failed" 79 | fi 80 | 81 | if [[ -e "$DEPLOYMENT_TEMP/__npmVersion.tmp" ]]; then 82 | NPM_JS_PATH=`cat "$DEPLOYMENT_TEMP/__npmVersion.tmp"` 83 | exitWithMessageOnError "getting npm version failed" 84 | fi 85 | 86 | if [[ ! -n "$NODE_EXE" ]]; then 87 | NODE_EXE=node 88 | fi 89 | 90 | NPM_CMD="\"$NODE_EXE\" \"$NPM_JS_PATH\"" 91 | else 92 | NPM_CMD=npm 93 | NODE_EXE=node 94 | fi 95 | } 96 | 97 | ################################################################################################################################## 98 | # Deployment 99 | # ---------- 100 | 101 | echo Handling node.js deployment. 102 | 103 | # 1. KuduSync 104 | if [[ "$IN_PLACE_DEPLOYMENT" -ne "1" ]]; then 105 | "$KUDU_SYNC_CMD" -v 50 -f "$DEPLOYMENT_SOURCE" -t "$DEPLOYMENT_TARGET" -n "$NEXT_MANIFEST_PATH" -p "$PREVIOUS_MANIFEST_PATH" -i ".git;.hg;.deployment;deploy.sh" 106 | exitWithMessageOnError "Kudu Sync failed" 107 | fi 108 | 109 | # 2. Select node version 110 | selectNodeVersion 111 | 112 | # 3. Install npm packages 113 | if [ -e "$DEPLOYMENT_TARGET/package.json" ]; then 114 | cd "$DEPLOYMENT_TARGET" 115 | eval $NPM_CMD install --production 116 | exitWithMessageOnError "npm failed" 117 | cd - > /dev/null 118 | fi 119 | 120 | # 4. Copy config-sample.js to config.js 121 | if [ -e "$DEPLOYMENT_TARGET/config-sample.js" ]; then 122 | cd "$DEPLOYMENT_TARGET" 123 | echo Creating config file... 124 | cp "config-sample.js" "config.js" 125 | cd - > /dev/null 126 | fi 127 | 128 | # 5. Build server 129 | cd "$DEPLOYMENT_TARGET" 130 | eval $NPM_CMD run build 131 | 132 | ################################################################################################################################## 133 | echo "Finished successfully." 134 | -------------------------------------------------------------------------------- /api/helpers/postgres.js: -------------------------------------------------------------------------------- 1 | import {Pool} from 'pg' 2 | // import Cursor from 'pg-cursor' 3 | import * as config from '../../config' 4 | 5 | /** 6 | * PostgresHelper 7 | */ 8 | class PostgresHelper { 9 | /** 10 | * Constructor 11 | */ 12 | constructor() { 13 | this.dbPool = new Pool(config.db) 14 | this.dbPool.connect() 15 | // this.cursors = {} 16 | } 17 | 18 | /** 19 | * Returns user's information fetched using the access token provide by the user. 20 | * @param {string} query user's access token 21 | * @param {string} params user's access token 22 | * @return {Promise} Fullfilled when user information is fetched 23 | */ 24 | execute(query, params) { 25 | console.log('Execute', query, params, '!!!!') 26 | return new Promise((resolve, reject) => { 27 | let select = query 28 | const result = {hasNext: true} 29 | 30 | const {where: where, paramList: paramList} = this.buildWhere(params) 31 | 32 | select += where 33 | 34 | // let cursor = this.getCursor(select, paramList) 35 | // cursor.read(config.max_query_result, (error, rows) => { 36 | // if (error) { 37 | // reject(error) 38 | // } 39 | // if (rows.length < config.max_query_result) { 40 | // this.removeCursor(cursor) 41 | // result.hasNext = false 42 | // } 43 | // result.rows = rows 44 | // result.count = rows.length 45 | // resolve(result) 46 | // }) 47 | this.dbPool.query(select, paramList) 48 | .then(queryResult => { 49 | if (queryResult.rowCount < config.max_query_result) { 50 | result.hasNext = false 51 | } 52 | result.rows = queryResult.rows 53 | result.count = queryResult.rowCount 54 | resolve(result) 55 | }) 56 | .catch(error => { 57 | reject(error) 58 | }) 59 | }) 60 | } 61 | 62 | /** 63 | * buildWhere. 64 | * @param {Object} params user's access token 65 | * @return{string} where clause 66 | */ 67 | buildWhere(params) { 68 | let group_by = null; 69 | if (params.group_by) { 70 | group_by = params.group_by; 71 | delete params.group_by 72 | } 73 | let where = '', 74 | paramList = [], 75 | count = 1, 76 | maxLimit = 0, 77 | offset = 0, 78 | page_number = 0 79 | page_number 80 | 81 | if ('offset' in params) { 82 | offset = params.offset 83 | delete params.offset 84 | } 85 | 86 | if ('page_number' in params && params.page_number > 0) { 87 | offset += (params.page_number - 1) * config.max_query_result 88 | delete params.page_number 89 | } 90 | 91 | if ('max_limit' in params && params.max_limit <= config.max_query_result) { 92 | maxLimit = params.max_limit 93 | delete params.max_limit 94 | } else { 95 | maxLimit = config.max_query_result 96 | } 97 | 98 | if ( Object.keys(params).length > 0) { 99 | let wherePart = ' WHERE lat is not null and lon is not ' + 100 | 'null and coords_within_country is true and ' 101 | wherePart += Object.keys(params).reduce((whereString, key) => { 102 | whereString += `${key} = $${count} and ` 103 | paramList.push(params[key]) 104 | count += 1 105 | return whereString 106 | }, '') 107 | 108 | where += wherePart.substring(0, wherePart.length - 5) 109 | } 110 | if (group_by) { 111 | where += ' group by ' + group_by; 112 | } 113 | let offsetStr = ' OFFSET $' + count 114 | paramList.push(offset) 115 | count += 1 116 | 117 | where += offsetStr 118 | 119 | if (maxLimit > 0) { 120 | let limit = ' LIMIT $' + count 121 | paramList.push(maxLimit) 122 | where += limit 123 | } 124 | 125 | return {where, paramList} 126 | } 127 | // 128 | // getCursor(query, params) { 129 | // let key = this.getQueryStatement(query, params) 130 | // if (!(key in this.cursors)) { 131 | // let cursor = new Cursor(query, params) 132 | // this.cursors[key] = this.dbClient.query(cursor) 133 | // } 134 | // 135 | // return this.cursors[key] 136 | // } 137 | // 138 | // removeCursor(cursor) { 139 | // let queryStatement = this.getQueryStatement(cursor.text, cursor.values) 140 | // if (queryStatement in this.cursors) { 141 | // cursor.close(() => {}) 142 | // delete this.cursors[queryStatement] 143 | // } 144 | // } 145 | // 146 | // getQueryStatement(query, params) { 147 | // let key = query 148 | // params.forEach((param, index) => { 149 | // key = key.replace(`$${index+1}`, param) 150 | // }) 151 | // return key 152 | // } 153 | } 154 | 155 | export default PostgresHelper 156 | -------------------------------------------------------------------------------- /api/helpers/auth0_rules.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is not used by any script in this repository. 3 | * This file documents functions (refered to as Rules) used by Auth0 4 | * to perform some operations in order to manage users and control access. 5 | * You can go to Auth0 dashboard to refer to these rules or update and add new rules. 6 | * URL: https://manage.auth0.com/#/rules 7 | * 8 | * Auth0 runs this functions in following order: 9 | * 1. addPropertyToUser 10 | * 2. setRoles 11 | * 3. trackLogins 12 | * 4. forceEmailVerifivation 13 | * 14 | * If any of these rules fails then Authentication Error is raised. 15 | * 16 | */ 17 | 18 | 19 | /** 20 | * Adds persistent attributes to the user. 21 | * Currently we use this rule to add "signedUp" attribute to user metadata. Using this we track the user is returning user or first time user. 22 | * @param {object} user User object with all user information 23 | * @param {object} context context object 24 | * @param {Function} callback callback function 25 | */ 26 | const addPropertyToUser = function (user, context, callback) { 27 | user.user_metadata = user.user_metadata || {}; 28 | 29 | if ('signedUp' in user.user_metadata) { 30 | user.user_metadata.signedUp = true; 31 | } else { 32 | user.user_metadata.signedUp = false; 33 | } 34 | 35 | auth0.users.updateUserMetadata(user.user_id, user.user_metadata) 36 | .then(() => { 37 | callback(null, user, context); 38 | }) 39 | .catch(error => { 40 | callback(error); 41 | }); 42 | } 43 | 44 | 45 | /** 46 | * Sets roles to to user. As of now rules are assigned depending on domain of the email address. 47 | * If it's unicef.org user gets "admin" access, else user gets "user" access. 48 | * @param {object} user User object with all user information 49 | * @param {object} context context object 50 | * @param {Function} callback callback function 51 | */ 52 | const setRoles = function (user, context, callback) { 53 | user.app_metadata = user.app_metadata || {}; 54 | // You can add a Role based on what you want 55 | // In this case I check domain 56 | let addRolesToUser = function(user, cb) { 57 | if (user.email && (user.email.indexOf('unicef.org') > -1)) { 58 | cb(null, ['admin']); 59 | } else { 60 | cb(null, ['user']); 61 | } 62 | }; 63 | 64 | addRolesToUser(user, function(err, roles) { 65 | if (err) { 66 | callback(err); 67 | } else { 68 | user.app_metadata.roles = roles; 69 | auth0.users.updateAppMetadata(user.user_id, user.app_metadata) 70 | .then(() => { 71 | context.idToken['magic-box/roles'] = user.app_metadata.roles; 72 | callback(null, user, context); 73 | }) 74 | .catch(error => { 75 | callback(error) 76 | }); 77 | } 78 | }); 79 | } 80 | 81 | 82 | /** 83 | * Tracks Logins in MixPanel. properties logged are clientIp, name, roles, 84 | * @param {object} user User object with all user information 85 | * @param {object} context context object 86 | * @param {Function} callback callback function 87 | * @return {[type]} [description] 88 | */ 89 | const trackLogins = function (user, context, callback) { 90 | // Property object 91 | let properties = { 92 | 'distinct_id': user.name, 93 | 'token': 'mixpanel_token_here', 94 | 'timestamp': new Date().toISOString(), 95 | 'clientIp': context.request.ip, 96 | 'name': user.name 97 | }; 98 | 99 | // mix-panel even object 100 | let mpEvent = {}; 101 | 102 | // check if user is signing up or logging in 103 | if (user.user_metadata.signedUp) { 104 | // if user is logging in then event is LOGIN, also add roles to properties 105 | mpEvent.event = 'LOGIN'; 106 | properties.roles = user.roles; 107 | } else { 108 | // else if user signing up then event is SIGNUP 109 | mpEvent.event = 'SIGNUP'; 110 | } 111 | 112 | // set properties of mix-panel event object 113 | mpEvent.properties = properties; 114 | 115 | // base64 String of mpEvent (not sure why, this template was provided by Auth0) 116 | let base64Event = new Buffer(JSON.stringify(mpEvent)).toString('base64'); 117 | 118 | // sending event to mix-panel 119 | request.get({ 120 | url: 'http://api.mixpanel.com/track/', 121 | qs: { 122 | data: base64Event 123 | } 124 | }, function(e, r, b) { 125 | // don’t wait for the MixPanel API call to finish, return right away (the request will continue on the sandbox)` 126 | callback(null, user, context); 127 | }); 128 | } 129 | 130 | /** 131 | * Checks if the user's email address is verified. If it isn't UnauthorizedError is sent back. 132 | * @param {object} user User object with all user information 133 | * @param {object} context context object 134 | * @param {Function} callback callback function 135 | */ 136 | const forceEmailVerifivation = function (user, context, callback) { 137 | if (!user.email_verified) { 138 | return callback( 139 | new UnauthorizedError('Please verify your email before logging in.') 140 | ); 141 | } else { 142 | return callback(null, user, context); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Magic Box API 2 | ============= 3 | 4 | [![Chat on Gitter](https://badges.gitter.im/unicef-innovation-dev/Lobby.png)](https://gitter.im/unicef-innovation-dev/Lobby) 5 | [![Build Status](https://travis-ci.org/unicef/magicbox-open-api.svg?branch=master)](https://travis-ci.org/unicef/magicbox-open-api) 6 | [![Maintainability](https://api.codeclimate.com/v1/badges/d36cba5a7e783ffd8970/maintainability)](https://codeclimate.com/github/unicef/magicbox-open-api/maintainability) 7 | 8 | [Magic Box](https://github.com/unicef/magicbox/wiki) is an open-source platform that is intended to use real-time information to inform life-saving humanitarian responses to emergency situations. It’s composed of multiple github repositories designed to ingest, aggregate, and serve data. 9 | 10 | ### Install the API that serves the data 11 | 12 | Magic Box API serves information useful to the data science team at the Office Of Innovation at UNICEF. This section describes how to install a local instance. It comes with sample data, but you can follow links below for code to download and aggregate many of the open data sets. 13 | 14 | Types of data currently available to the public include: 15 | 16 | - population 17 | - mosquito prevalence 18 | - Paho Zika case data 19 | - School location and connectivity 20 | 21 | ...aggregated at municipal, state, and national levels. 22 | 23 | 24 | ### Dependencies: 25 | #### NVM 26 | curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.32.1/install.sh | bash 27 | #### Node.js 28 | nvm install 8 29 | ## Setup 30 | 31 | git clone https://github.com/unicef/magicbox-open-api.git 32 | cd magicbox-open-api 33 | cp config-sample.js config.js 34 | npm install 35 | npm run build 36 | npm run start 37 | 38 | Now browse to: localhost:8000/docs 39 | 40 | ![Screenshot](https://github.com/unicef/magicbox-open-api/blob/master/public/images/expand_pop.gif) 41 | 42 | The first endpoint: /api/v1/population/countries, returns a list of codes for countries for which we have population data: 43 | ```` 44 | [ ‘afg’, ‘ago’, ‘arg’ … ‘zwe’] 45 | ```` 46 | 47 | The second endpoint returns population data for a single country. For instance, to fetch the population for Afghanistan at the district level, browse to: localhost:8000/api/v1/population/countries/afg 48 | 49 | [ 50 | { admin_id: ‘afg_1_5_50_gadm2–8’, value: 165297 }, 51 | { admin_id: ‘afg_1_4_33_gadm2–8’, value: 175117 }, 52 | { admin_id: ‘afg_1_7_57_gadm2–8’, value: 49994}, 53 | { admin_id: ‘afg_1_11_102_gadm2–8’, value: 50304 }, 54 | 55 | … 228 more items 56 | ] 57 | 58 | ### What does this mean? 59 | The value points to number of people. But, what swath of land does each admin_id refer to? 60 | 61 | Answer: An admin ID points to a specific shape in a shapefile that represents an individual country. 62 | 63 | ![Screenshot](https://github.com/unicef/magicbox-open-api/blob/master/public/images/afg_shapefile.png) 64 | 65 | To understand where afg_1_11_102-gadm2–8 points to, first note that Afghanistan has three levels of administrative boundaries: 66 | 67 | ![Screenshot](https://github.com/unicef/magicbox-open-api/blob/master/public/images/admin_levels.png) 68 | 69 | The admin_id has three integers, one per admin level: 70 | 71 | ![Screenshot](https://github.com/unicef/magicbox-open-api/blob/master/public/images/admin_levels_arrows.png) 72 | 73 | The first integer is 1 because Afghanistan is the first country in the collection of 254 available at gadm.org. 74 | 75 | ![Screenshot](https://github.com/unicef/magicbox-open-api/blob/master/public/images/afg_thru_zwe.png) 76 | 77 | As for the second and third integers, Afghanistan has 34 shapes at admin level one (each shape is assigned an ID), and 320 shapes in admin 2. 78 | 79 | ![Screenshot](https://github.com/unicef/magicbox-open-api/blob/master/public/images/admin_1_and_2v2.png) 80 | 81 | Thus, afg_1_11_102-gadm2–8 indicates that any population value attached to it is related to: 82 | 83 | - The first country in the gadm collection. 84 | - The 11th shape in the admin 1 level shapefile. 85 | - The 102nd shape in the admin 2 level shapefile. 86 | 87 | ![Screenshot](https://github.com/unicef/magicbox-open-api/blob/master/public/images/admin_id_explain_all.png) 88 | 89 | ### Mosquito Prevalence (University of Oxford) 90 | 91 | ![Screenshot](https://github.com/unicef/magicbox-open-api/blob/master/public/images/mos_endpoints.png) 92 | 93 | - Currently, the API serves prevalence scores at both a national and district/province level per country. Scores range from 0 to 1. Browse to localhost:8000/api/v1/mosquito/kinds to see what mosquito types we have data for: 94 | 95 | ```` 96 | [ ‘aegypti’, ‘albopictus’ ] 97 | ```` 98 | - Browse to localhost:8000/api/v1/mosquito/kinds/aegypti for a country by country list: 99 | ```` 100 | { 101 | abw: 0.92733, 102 | afg: 0.12469, 103 | … 104 | zwe: 0.54493 105 | } 106 | ```` 107 | 108 | Similar to Population, you can also use: 109 | 110 | - /api/v1/mosquito/kinds/aegypti/countries to retrieve a list of country_codes 111 | - /api/v1/mosquito/kinds/aegypti/countries/afg/ to get mosquito prevalence scores per district. 112 | 113 | ### Zika Case Data (Paho) 114 | 115 | The API serves Zika case data for the Americas (national level) as published by the Pan American Health Organization in excel files each epi week. In order to overlay with travel data given to us by Amadeus, we’ve also used a *really* simple algorithm to group the cases by ISO week. 116 | 117 | ![Screenshot](https://github.com/unicef/magicbox-open-api/blob/master/public/images/epi_iso.png) 118 | 119 | To fetch all zika case data to date for either week type, use these end points: 120 | - localhost:8000/api/v1/cases/kinds/zika/weekTypes/iso 121 | - localhost:8000/api/v1/cases/kinds/zika/weekTypes/epi 122 | 123 | ![Screenshot](https://github.com/unicef/magicbox-open-api/blob/master/public/images/case_output.png) 124 | 125 | #### To serve the same data as the [live API](http://magicbox-open-api.azurewebsites.net/docs), follow the [Magic Box Wiki](https://github.com/unicef/magicbox/wiki)! 126 | -------------------------------------------------------------------------------- /POP.csv: -------------------------------------------------------------------------------- 1 | iso,rank,name,population 2 | CHN,1,China,"1,371,220" 3 | IND,2,India,"1,311,051" 4 | USA,3,United States,"321,419" 5 | IDN,4,Indonesia,"257,564" 6 | BRA,5,Brazil,"207,848" 7 | PAK,6,Pakistan,"188,925" 8 | NGA,7,Nigeria,"182,202" 9 | BGD,8,Bangladesh,"160,996" 10 | RUS,9,Russian Federation,"144,097" 11 | MEX,10,Mexico,"127,017" 12 | JPN,11,Japan,"126,958" 13 | PHL,12,Philippines,"100,699" 14 | ETH,13,Ethiopia,"99,391" 15 | VNM,14,Vietnam,"91,713" 16 | EGY,15,"Egypt, Arab Rep.","91,508" 17 | DEU,16,Germany,"81,680" 18 | IRN,17,"Iran, Islamic Rep.","79,109" 19 | TUR,18,Turkey,"78,666" 20 | COD,19,"Congo, Dem. Rep.","77,267" 21 | THA,20,Thailand,"67,959" 22 | FRA,21,France,"66,538" 23 | GBR,22,United Kingdom,"65,129" 24 | ITA,23,Italy,"60,731" 25 | ZAF,24,South Africa,"55,012" 26 | MMR,25,Myanmar,"53,897" 27 | TZA,26,Tanzania,"53,470" 28 | KOR,27,"Korea, Rep.","50,617" 29 | COL,28,Colombia,"48,229" 30 | ESP,29,Spain,"46,444" 31 | KEN,30,Kenya,"46,050" 32 | UKR,31,Ukraine,"45,154" 33 | ARG,32,Argentina,"43,417" 34 | SDN,33,Sudan,"40,235" 35 | DZA,34,Algeria,"39,667" 36 | UGA,35,Uganda,"39,032" 37 | POL,36,Poland,"37,986" 38 | IRQ,37,Iraq,"36,423" 39 | CAN,38,Canada,"35,849" 40 | MAR,39,Morocco,"34,378" 41 | AFG,40,Afghanistan,"32,527" 42 | SAU,41,Saudi Arabia,"31,540" 43 | PER,42,Peru,"31,377" 44 | UZB,43,Uzbekistan,"31,299" 45 | VEN,44,"Venezuela, RB","31,108" 46 | MYS,45,Malaysia,"30,331" 47 | NPL,46,Nepal,"28,514" 48 | MOZ,47,Mozambique,"27,978" 49 | GHA,48,Ghana,"27,410" 50 | YEM,49,"Yemen, Rep.","26,832" 51 | PRK,50,"Korea, Dem. People's Rep.","25,155" 52 | AGO,51,Angola,"25,022" 53 | MDG,52,Madagascar,"24,235" 54 | AUS,53,Australia,"23,790" 55 | CMR,54,Cameroon,"23,344" 56 | CIV,55,Côte d'Ivoire,"22,702" 57 | LKA,56,Sri Lanka,"20,966" 58 | NER,57,Niger,"19,899" 59 | ROU,58,Romania,"19,815" 60 | SYR,59,Syrian Arab Republic,"18,502" 61 | BFA,60,Burkina Faso,"18,106" 62 | CHL,61,Chile,"17,948" 63 | MLI,62,Mali,"17,600" 64 | KAZ,63,Kazakhstan,"17,544" 65 | MWI,64,Malawi,"17,215" 66 | NLD,65,Netherlands,"16,940" 67 | GTM,66,Guatemala,"16,343" 68 | ZMB,67,Zambia,"16,212" 69 | ECU,68,Ecuador,"16,144" 70 | ZWE,69,Zimbabwe,"15,603" 71 | KHM,70,Cambodia,"15,578" 72 | SEN,71,Senegal,"15,129" 73 | TCD,72,Chad,"14,037" 74 | GIN,73,Guinea,"12,609" 75 | SSD,74,South Sudan,"12,340" 76 | RWA,75,Rwanda,"11,610" 77 | CUB,76,Cuba,"11,390" 78 | TUN,77,Tunisia,"11,254" 79 | BEL,78,Belgium,"11,249" 80 | BDI,79,Burundi,"11,179" 81 | BEN,80,Benin,"10,880" 82 | GRC,81,Greece,"10,821" 83 | SOM,82,Somalia,"10,787" 84 | BOL,83,Bolivia,"10,725" 85 | HTI,84,Haiti,"10,711" 86 | CZE,85,Czech Republic,"10,546" 87 | DOM,86,Dominican Republic,"10,528" 88 | PRT,87,Portugal,"10,358" 89 | HUN,88,Hungary,"9,843" 90 | SWE,89,Sweden,"9,799" 91 | AZE,90,Azerbaijan,"9,649" 92 | BLR,91,Belarus,"9,490" 93 | ARE,92,United Arab Emirates,"9,157" 94 | AUT,93,Austria,"8,638" 95 | TJK,94,Tajikistan,"8,482" 96 | ISR,95,Israel,"8,380" 97 | CHE,96,Switzerland,"8,281" 98 | HND,97,Honduras,"8,075" 99 | PNG,98,Papua New Guinea,"7,619" 100 | JOR,99,Jordan,"7,595" 101 | HKG,100,"Hong Kong SAR, China","7,306" 102 | TGO,101,Togo,"7,305" 103 | BGR,102,Bulgaria,"7,178" 104 | SRB,103,Serbia,"7,095" 105 | LAO,104,Lao PDR,"6,802" 106 | PRY,105,Paraguay,"6,639" 107 | SLE,106,Sierra Leone,"6,453" 108 | LBY,107,Libya,"6,278" 109 | SLV,108,El Salvador,"6,127" 110 | NIC,109,Nicaragua,"6,082" 111 | KGZ,110,Kyrgyz Republic,"5,957" 112 | LBN,111,Lebanon,"5,851" 113 | DNK,112,Denmark,"5,683" 114 | SGP,113,Singapore,"5,535" 115 | FIN,114,Finland,"5,480" 116 | SVK,115,Slovak Republic,"5,424" 117 | TKM,116,Turkmenistan,"5,374" 118 | ERI,117,Eritrea,"5,228" 119 | NOR,118,Norway,"5,190" 120 | CAF,119,Central African Republic,"4,900" 121 | CRI,120,Costa Rica,"4,808" 122 | IRL,121,Ireland,"4,644" 123 | COG,122,"Congo, Rep.","4,620" 124 | NZL,123,New Zealand,"4,596" 125 | LBR,124,Liberia,"4,503" 126 | OMN,125,Oman,"4,491" 127 | PSE,126,West Bank and Gaza,"4,422" 128 | HRV,127,Croatia,"4,204" 129 | MRT,128,Mauritania,"4,068" 130 | PAN,129,Panama,"3,929" 131 | KWT,130,Kuwait,"3,892" 132 | BIH,131,Bosnia and Herzegovina,"3,810" 133 | GEO,132,Georgia,"3,717" 134 | MDA,133,Moldova,"3,554" 135 | PRI,134,Puerto Rico,"3,474" 136 | URY,135,Uruguay,"3,432" 137 | ARM,136,Armenia,"3,018" 138 | MNG,137,Mongolia,"2,959" 139 | LTU,138,Lithuania,"2,905" 140 | ALB,139,Albania,"2,889" 141 | JAM,140,Jamaica,"2,793" 142 | NAM,141,Namibia,"2,459" 143 | BWA,142,Botswana,"2,262" 144 | QAT,143,Qatar,"2,235" 145 | LSO,144,Lesotho,"2,135" 146 | MKD,145,"Macedonia, FYR","2,078" 147 | SVN,146,Slovenia,"2,064" 148 | GMB,147,"Gambia, The","1,991" 149 | LVA,148,Latvia,"1,978" 150 | GNB,149,Guinea-Bissau,"1,844" 151 | XKX,150,Kosovo,"1,802" 152 | GAB,151,Gabon,"1,725" 153 | BHR,152,Bahrain,"1,377" 154 | TTO,153,Trinidad and Tobago,"1,360" 155 | EST,154,Estonia,"1,315" 156 | SWZ,155,Swaziland,"1,287" 157 | MUS,156,Mauritius,"1,263" 158 | TLS,157,Timor-Leste,"1,185" 159 | CYP,158,Cyprus,"1,165" 160 | FJI,159,Fiji,892 161 | DJI,160,Djibouti,888 162 | GNQ,161,Equatorial Guinea,845 163 | COM,162,Comoros,788 164 | BTN,163,Bhutan,775 165 | GUY,164,Guyana,767 166 | MNE,165,Montenegro,622 167 | MAC,166,"Macao SAR, China",588 168 | SLB,167,Solomon Islands,584 169 | LUX,168,Luxembourg,570 170 | SUR,169,Suriname,543 171 | CPV,170,Cabo Verde,521 172 | MLT,171,Malta,432 173 | BRN,172,Brunei Darussalam,423 174 | MDV,173,Maldives,409 175 | BHS,174,"Bahamas, The",388 176 | BLZ,175,Belize,359 177 | ISL,176,Iceland,331 178 | BRB,177,Barbados,284 179 | PYF,178,French Polynesia,283 180 | NCL,179,New Caledonia,273 181 | VUT,180,Vanuatu,265 182 | WSM,181,Samoa,193 183 | STP,182,São Tomé and Principe,190 184 | LCA,183,St. Lucia,185 185 | GUM,184,Guam,170 186 | CHI,185,Channel Islands,164 187 | CUW,186,Curaçao,158 188 | KIR,187,Kiribati,112 189 | VCT,188,St. Vincent and the Grenadines,109 190 | GRD,189,Grenada,107 191 | TON,190,Tonga,106 192 | FSM,191,"Micronesia, Fed. Sts.",104 193 | ABW,192,Aruba,104 194 | VIR,193,Virgin Islands (U.S.),104 195 | SYC,194,Seychelles,93 196 | ATG,195,Antigua and Barbuda,92 197 | IMN,196,Isle of Man,88 198 | DMA,197,Dominica,73 199 | AND,198,Andorra,70 200 | BMU,199,Bermuda,65 201 | CYM,200,Cayman Islands,60 202 | GRL,201,Greenland,56 203 | KNA,202,St. Kitts and Nevis,56 204 | ASM,203,American Samoa,56 205 | MNP,204,Northern Mariana Islands,55 206 | MHL,205,Marshall Islands,53 207 | FRO,206,Faroe Islands,48 208 | SXM,207,Sint Maarten (Dutch part),39 209 | MCO,208,Monaco,38 210 | LIE,209,Liechtenstein,38 211 | TCA,210,Turks and Caicos Islands,34 212 | GIB,211,Gibraltar,32 213 | SMR,212,San Marino,32 214 | MAF,213,St. Martin (French part),32 215 | VGB,214,British Virgin Islands,30 216 | PLW,215,Palau,21 217 | NRU,216,Nauru,12 218 | TUV,217,Tuvalu,10 219 | -------------------------------------------------------------------------------- /test/api-test/api_test.js: -------------------------------------------------------------------------------- 1 | import chaiHttp from 'chai-http' 2 | import server from '../../server.js' 3 | import chai from 'chai' 4 | const should = chai.should() 5 | let source = 'The global distribution of the arbovirus vectors' 6 | source += ' Aedes aegypti and Ae. albopictus' 7 | 8 | const source_url = 'https://elifesciences.org/content/4/e08347' 9 | 10 | process.env.NODE_ENV = 'test' 11 | 12 | chai.use(chaiHttp); 13 | 14 | describe('testing mobility API', () => { 15 | it('it should GET list of objects', (done) => { 16 | chai.request(server) 17 | .get('/api/v1/mobility/countries/') 18 | .end((err, res) => { 19 | res.should.have.status(200) 20 | res.body.should.be.a('array') 21 | done() 22 | }) 23 | }) 24 | }) 25 | 26 | describe('testing population API', () => { 27 | it('it should GET list of countries', (done) => { 28 | chai.request(server) 29 | .get('/api/v1/population/countries/') 30 | .end((err, res) => { 31 | res.should.have.status(200) 32 | res.body.should.be.a('object') 33 | res.body.should.have.property('key').eql('population_worldpop') 34 | res.body.should.have.property('properties') 35 | res.body.properties.should.be.a('array') 36 | res.body.properties[0].should.equal('afg') 37 | done() 38 | }) 39 | }) 40 | 41 | 42 | it('it should GET population of AFG', (done) => { 43 | const firstValue = {admin_id: 'afg_1_12_129_gadm2-8', 44 | value: 74459.9892636929, 45 | ID_0: 1, 46 | ISO: 'AFG', 47 | NAME_0: 'Afghanistan', 48 | ID_1: 12, 49 | NAME_1: 'Hirat', 50 | ID_2: 129, 51 | NAME_2: 'Zinda Jan', 52 | HASC_2: 'AF.HR.ZJ', 53 | CCN_2: 0, 54 | CCA_2: null, 55 | TYPE_2: 'Wuleswali', 56 | ENGTYPE_2: 'District', 57 | NL_NAME_2: null, 58 | VARNAME_2: null 59 | } 60 | 61 | chai.request(server) 62 | .get('/api/v1/population/countries/afg') 63 | .end((err, res) => { 64 | res.should.have.status(200) 65 | res.body.should.be.a('object') 66 | res.body.should.have.property('key').eql('population') 67 | res.body.should.have.property('source').eql('worldpop') 68 | res.body.should.have.property('data') 69 | res.body.data.should.be.a('object') 70 | res.body.data.should.have.property('source').eql('worldpop') 71 | res.body.data.should.have.property('raster').eql('popmap15adj') 72 | res.body.data.should.have.property('values') 73 | res.body.data.values.should.be.a('array') 74 | chai.expect(res.body.data.values[0]).to.deep.equal(firstValue) 75 | done() 76 | }) 77 | }) 78 | }) 79 | 80 | 81 | describe('testing mosquito API', () => { 82 | it('it should GET list of mosquito kinds', (done) => { 83 | chai.request(server) 84 | .get('/api/v1/mosquito/kinds/') 85 | .end((err, res) => { 86 | res.should.have.status(200) 87 | res.body.should.be.a('object') 88 | res.body.should.have.property('key').eql('mosquito') 89 | res.body.should.have.property('properties') 90 | res.body.properties.should.be.a('array') 91 | res.body.properties[0].should.equal('aegypti') 92 | done() 93 | }) 94 | }) 95 | 96 | 97 | it('it should GET mosquito prevalence of kind specified', (done) => { 98 | const afg_mosquito = {afg: 99 | [{country: 'afg', 100 | data_source: 'simon_hay', 101 | shapefile: 'gadm2-8', 102 | admin_level: '2', 103 | sum: 0.12469, 104 | sq_km: 376427, 105 | density: 3.312461645949945e-7, 106 | raster: 'aegypti'}] 107 | } 108 | 109 | chai.request(server) 110 | .get('/api/v1/mosquito/kinds/aegypti') 111 | .end((err, res) => { 112 | res.should.have.status(200) 113 | res.body.should.be.a('object') 114 | res.body.should.have.property('key').eql('mosquito') 115 | res.body.should.have.property('kind').eql('aegypti') 116 | res.body.should.have.property('source').eql(source) 117 | res.body.should.have.property('source_url').eql(source_url) 118 | res.body.should.have.property('data') 119 | res.body.data.should.be.a('object') 120 | chai.expect(res.body.data).to.deep.equal(afg_mosquito) 121 | done() 122 | }) 123 | }) 124 | 125 | it('it should GET list of countries', (done) => { 126 | chai.request(server) 127 | .get('/api/v1/mosquito/kinds/aegypti/countries/') 128 | .end((err, res) => { 129 | res.should.have.status(200) 130 | res.body.should.be.a('object') 131 | res.body.should.have.property('key').eql('mosquito_aegypti') 132 | res.body.should.have.property('properties') 133 | res.body.properties.should.be.a('array') 134 | res.body.properties[0].should.equal('afg') 135 | done() 136 | }) 137 | }) 138 | 139 | 140 | it('it should GET mosquito prevalence in a country of specified kind', 141 | (done) => { 142 | const afg_mosquito = {admin_id: 'afg_1_16_170_gadm2-8', 143 | value: 0.134451949145065, 144 | ID_0: 1, 145 | ISO: 'AFG', 146 | NAME_0: 'Afghanistan', 147 | ID_1: 16, 148 | NAME_1: 'Kapisa', 149 | ID_2: 170, 150 | NAME_2: 'Nijrab', 151 | HASC_2: 'AF.KP.NI', 152 | CCN_2: 0, 153 | CCA_2: null, 154 | TYPE_2: 'Wuleswali', 155 | ENGTYPE_2: 'District', 156 | NL_NAME_2: null, 157 | VARNAME_2: null 158 | } 159 | 160 | chai.request(server) 161 | .get('/api/v1/mosquito/kinds/aegypti/countries/afg') 162 | .end((err, res) => { 163 | res.should.have.status(200) 164 | res.body.should.be.a('object') 165 | res.body.should.have.property('key').eql('mosquito') 166 | res.body.should.have.property('kind').eql('aegypti') 167 | res.body.should.have.property('source').eql(source) 168 | res.body.should.have.property('source_url').eql(source_url) 169 | res.body.should.have.property('data') 170 | res.body.data.should.be.a('object') 171 | res.body.data.should.have.property('source').eql('simon_hay') 172 | res.body.data.should.have.property('raster').eql('aegypti') 173 | res.body.data.should.have.property('values') 174 | res.body.data.values.should.be.a('array') 175 | chai.expect(res.body.data.values[0]).to.deep.equal(afg_mosquito) 176 | done() 177 | }) 178 | }) 179 | }) 180 | -------------------------------------------------------------------------------- /api/helpers/auth.js: -------------------------------------------------------------------------------- 1 | import config from '../../config' 2 | import request from 'request' 3 | import authZeroWeb from 'auth0-js' 4 | import authZero from 'auth0' 5 | import * as logger from './../helpers/logger' 6 | import isJSON from 'is-json' 7 | import moment from 'moment' 8 | const tokenPrefix = 'Bearer ' 9 | const keyScope = 'x-security-scopes' 10 | const keyRoles = 'magic-box/roles' 11 | 12 | let seen_users = {}; 13 | 14 | const authProperties = { 15 | domain: config.auth0.auth_domain, 16 | clientID: config.auth0.client_id 17 | } 18 | 19 | let webAuth = new authZeroWeb.WebAuth(authProperties) 20 | let authClient = new authZero.AuthenticationClient(authProperties) 21 | 22 | /** 23 | * Returns authorisation url which redirects user to Auth0's website. 24 | * User can register or login and get access token from Auth0 website 25 | * @return {string} url authorisation url 26 | */ 27 | export const getAuthorizeUrl = () => { 28 | let url = webAuth.client.buildAuthorizeUrl({ 29 | responseType: 'token', 30 | redirectUri: config.auth0.callback_url, 31 | state: 'innovation', 32 | responseMode: 'form_post', 33 | scope: 'openid' 34 | }) 35 | return url 36 | } 37 | 38 | /** 39 | * Returns user's information fetched using the access token provide by the user. 40 | * @param {string} access_token user's access token 41 | * @return {boolean} User was seen within a certain amount of time. 42 | */ 43 | function user_seen(access_token) { 44 | console.log('Taking a look', access_token) 45 | if (seen_users[access_token] && seen_users[access_token].timestamp) { 46 | let time_registered = (moment.now() - seen_users[access_token].timestamp) 47 | console.log('User has been seen', time_registered, 'ago') 48 | if (time_registered < 6000000) { 49 | console.log('Still within reasonable time') 50 | return true; 51 | } else { 52 | console.log('Delete user') 53 | delete seen_users[access_token]; 54 | return false 55 | } 56 | } 57 | return false 58 | } 59 | 60 | /** 61 | * Returns user's information fetched using the access token provide by the user. 62 | * @param {string} token user's access token 63 | * @return {Promise} Fullfilled when user information is fetched 64 | */ 65 | export const getUserInfo = (token) => { 66 | return new Promise((resolve, reject) => { 67 | console.log('Check if user has been seen') 68 | if (user_seen(token)) { 69 | console.log('No need to hit Auth0') 70 | return resolve(seen_users[token]) 71 | } 72 | console.log('User has not been seen') 73 | authClient.getProfile(token) 74 | .then(userInfo => { 75 | if (userInfo === 'Unauthorized') { 76 | console.error('userInfo is Unauthorized') 77 | // If access token is bad 78 | // userInfo returns as "Unauthoraized" 79 | resolve( 80 | {error: userInfo} 81 | ) 82 | } 83 | if (userInfo === 'Too Many Requests') { 84 | console.error('userInfo is Too Many Requests') 85 | // If access token is bad 86 | // userInfo returns as "Unauthoraized" 87 | resolve({error: userInfo}) 88 | } 89 | // if (!isJSON(userInfo)) { 90 | // console.log("NOT JSON", userInfo) 91 | // // If access token is bad 92 | // // userInfo returns as "Unauthoraized" 93 | // userInfo = {message: userInfo} 94 | // } 95 | 96 | if (typeof userInfo === 'string') { 97 | if (isJSON(userInfo)) { 98 | userInfo = JSON.parse(userInfo); 99 | return resolve(userInfo) 100 | } else { 101 | console.log('Not json string') 102 | } 103 | } 104 | logger.log('userInfo', userInfo) 105 | return resolve(userInfo) 106 | }) 107 | .catch(reject) 108 | }) 109 | } 110 | 111 | /** 112 | * Verifies if user has required level of authorisation 113 | * @param {object} req request object 114 | * @param {object} authOrSecDef auth and security definations from swagger file 115 | * @param {string} token token string provided with request 116 | * @param {Function} callback callback function 117 | * @return {Array} user roles 118 | */ 119 | export const verifyToken = (req, authOrSecDef, token, callback) => { 120 | let errorObject = {message: 'Access Denied. Please check your token'} 121 | 122 | if (token && token.indexOf(tokenPrefix) !== -1) { 123 | let accessToken = token.substring( 124 | token.indexOf(tokenPrefix) + tokenPrefix.length 125 | ) 126 | console.error('Access token', accessToken) 127 | // get all the required roles from swagger doc. 128 | let requiredRoles = req.swagger.operation[keyScope] 129 | 130 | getUserInfo(accessToken) 131 | .then(userInfo => { 132 | if (userInfo.error) { 133 | return callback(userInfo) 134 | } 135 | 136 | if (!seen_users[token]) { 137 | console.log('Add user to hash') 138 | Object.assign(userInfo, {timestamp: Date.now()}) 139 | seen_users[accessToken] = userInfo; 140 | } 141 | let userRoles = userInfo[keyRoles] 142 | if (!userRoles) { 143 | if (userInfo.email && userInfo.email_verified) { 144 | console.log('Email all good') 145 | let email_domain = userInfo.email.split('@'); 146 | if (config.auth0.roles[email_domain[1]]) { 147 | console.log('Assign some roles', email_domain[1]) 148 | userRoles = [config.auth0.roles[email_domain[1]]] 149 | console.log('User roles', userRoles) 150 | } 151 | } 152 | } 153 | console.log('Required roles', requiredRoles) 154 | // check if user has all the required roles 155 | let verified = requiredRoles.every(role => { 156 | return userRoles.indexOf(role) >= 0 157 | }) 158 | 159 | // check if user is verified or if he is admin 160 | if (verified || userRoles.indexOf('admin') !== -1) { 161 | console.log('Verified OR user is admin') 162 | return callback(null) 163 | } else { 164 | console.error('111 check if user is verified or if he is admin', 165 | errorObject, userRoles) 166 | Object.assign(errorObject, {second: '111'}) 167 | return callback(errorObject) 168 | } 169 | }) 170 | .catch(error => { 171 | Object.assign(errorObject, {second: '222'}) 172 | return callback(errorObject) 173 | }) 174 | } else { 175 | Object.assign(errorObject, {second: '3333'}) 176 | return callback(errorObject) 177 | } 178 | } 179 | 180 | /** 181 | * Verifies if user has required level of authorisation 182 | * @param {object} code fetched from auth0 183 | * @return {object} object with ip, path and query of the request 184 | */ 185 | export const getRefreshToken = code => { 186 | return new Promise((resolve, reject) => { 187 | let options = {method: 'POST', 188 | url: config.auth0.auth_url, 189 | headers: {'content-type': 'application/json'}, 190 | body: 191 | {grant_type: 'authorization_code', 192 | client_id: config.auth0.client_id, 193 | client_secret: config.auth0.client_secret, 194 | scope: 'profile+roles', 195 | code: code, 196 | redirect_uri: config.auth0.redirect_uri}, 197 | json: true}; 198 | 199 | request(options, function(error, response, body) { 200 | if (error) throw new Error(error); 201 | // Object has refresh token 202 | return resolve(body); 203 | }); 204 | }) 205 | } 206 | 207 | /** 208 | * Gets new access token 209 | * @param {object} refresh_token fetched from auth0 210 | * @return {object} object with ip, path and query of the request 211 | */ 212 | export const refreshAccessToken = refresh_token => { 213 | return new Promise((resolve, reject) => { 214 | let options = {method: 'POST', 215 | url: config.auth0.auth_url, 216 | headers: {'content-type': 'application/json'}, 217 | body: 218 | {grant_type: 'refresh_token', 219 | client_id: config.auth0.client_id, 220 | client_secret: config.auth0.client_secret, 221 | responseType: 'token id_token', 222 | refresh_token: refresh_token, 223 | state: 'innovation', 224 | redirect_uri: config.auth0.redirect_uri}, 225 | json: true}; 226 | 227 | request(options, function(error, response, body) { 228 | if (error) throw new Error(error); 229 | return resolve(body); 230 | }); 231 | }) 232 | } 233 | -------------------------------------------------------------------------------- /public/aggregations/cases/zika/paho/epi/2016-11-17.json: -------------------------------------------------------------------------------- 1 | {"date":"2016-11-17","countries":{"can":{"country":"Canada","autochthonous_cases_suspected":0,"autochthonous_cases_confirmed":0,"imported_cases":374,"incidence_rate":0,"deaths":0,"confirmed_congenital":0,"population_x_1k":36286,"congenital_suspected":0,"congenital_probable":0,"gbs_total":0,"gbs_confirmed":0},"usa":{"country":"United States of America","autochthonous_cases_suspected":0,"autochthonous_cases_confirmed":140,"imported_cases":4115,"incidence_rate":0.04319401207581166,"deaths":0,"confirmed_congenital":31,"population_x_1k":324119,"congenital_suspected":0,"congenital_probable":0,"gbs_total":0,"gbs_confirmed":7},"mex":{"country":"Mexico","autochthonous_cases_suspected":0,"autochthonous_cases_confirmed":6474,"imported_cases":15,"incidence_rate":5.032962248896076,"deaths":0,"confirmed_congenital":0,"population_x_1k":128632,"congenital_suspected":0,"congenital_probable":0,"gbs_total":419,"gbs_confirmed":0},"cri":{"country":"Costa Rica","autochthonous_cases_suspected":2563,"autochthonous_cases_confirmed":1415,"imported_cases":32,"incidence_rate":81.90240889437925,"deaths":0,"confirmed_congenital":1,"population_x_1k":4857,"congenital_suspected":0,"congenital_probable":0,"gbs_total":0,"gbs_confirmed":1},"slv":{"country":"El Salvador","autochthonous_cases_suspected":11333,"autochthonous_cases_confirmed":51,"imported_cases":0,"incidence_rate":185.2261633582818,"deaths":0,"confirmed_congenital":4,"population_x_1k":6146},"gtm":{"country":"Guatemala","autochthonous_cases_suspected":2785,"autochthonous_cases_confirmed":466,"imported_cases":0,"incidence_rate":19.498590535596474,"deaths":0,"confirmed_congenital":15,"population_x_1k":16673,"congenital_suspected":5,"congenital_probable":0,"gbs_total":47,"gbs_confirmed":13},"hnd":{"country":"Honduras","autochthonous_cases_suspected":31719,"autochthonous_cases_confirmed":285,"imported_cases":0,"incidence_rate":390.7692307692308,"deaths":0,"confirmed_congenital":1,"population_x_1k":8190,"congenital_suspected":61,"congenital_probable":0,"gbs_total":141,"gbs_confirmed":1},"nic":{"country":"Nicaragua","autochthonous_cases_suspected":0,"autochthonous_cases_confirmed":2038,"imported_cases":3,"incidence_rate":33.13821138211382,"deaths":0,"confirmed_congenital":0,"population_x_1k":6150},"pan":{"country":"Panama","autochthonous_cases_suspected":2089,"autochthonous_cases_confirmed":517,"imported_cases":42,"incidence_rate":65.31328320802005,"deaths":0,"confirmed_congenital":5,"population_x_1k":3990,"congenital_suspected":35,"congenital_probable":5,"gbs_total":13,"gbs_confirmed":3},"cub":{"country":"Cuba","autochthonous_cases_suspected":0,"autochthonous_cases_confirmed":3,"imported_cases":30,"incidence_rate":0.026331958219959626,"deaths":0,"confirmed_congenital":0,"population_x_1k":11393,"gbs_total":29},"dom":{"country":"Dominican Republic","autochthonous_cases_suspected":4889,"autochthonous_cases_confirmed":328,"imported_cases":0,"incidence_rate":48.99051554136538,"deaths":0,"confirmed_congenital":10,"population_x_1k":10649,"congenital_suspected":16,"congenital_probable":0,"gbs_total":274,"gbs_confirmed":30},"guf":{"country":"French Guiana","autochthonous_cases_suspected":9700,"autochthonous_cases_confirmed":483,"imported_cases":10,"incidence_rate":3689.4927536231885,"deaths":0,"confirmed_congenital":14,"population_x_1k":276},"glp":{"country":"Guadeloupe","autochthonous_cases_suspected":30845,"autochthonous_cases_confirmed":379,"imported_cases":0,"incidence_rate":6629.299363057325,"deaths":0,"confirmed_congenital":1,"population_x_1k":471},"hti":{"country":"Haiti","autochthonous_cases_suspected":2955,"autochthonous_cases_confirmed":5,"imported_cases":0,"incidence_rate":27.28613569321534,"deaths":0,"confirmed_congenital":1,"population_x_1k":10848},"mtq":{"country":"Martinique","autochthonous_cases_suspected":36680,"autochthonous_cases_confirmed":12,"imported_cases":0,"incidence_rate":9265.656565656565,"deaths":0,"confirmed_congenital":14,"population_x_1k":396},"pri":{"country":"Puerto Rico","autochthonous_cases_suspected":0,"autochthonous_cases_confirmed":33455,"imported_cases":1,"incidence_rate":908.8562890518881,"deaths":5,"confirmed_congenital":3,"population_x_1k":3681,"congenital_suspected":0,"congenital_probable":1,"gbs_total":11,"gbs_confirmed":34},"blm":{"country":"Saint Barthelemy","autochthonous_cases_suspected":900,"autochthonous_cases_confirmed":61,"imported_cases":0,"incidence_rate":10677.777777777777,"deaths":0,"confirmed_congenital":0,"population_x_1k":9},"maf":{"country":"Saint Martin","autochthonous_cases_suspected":2825,"autochthonous_cases_confirmed":200,"imported_cases":0,"incidence_rate":8402.777777777777,"deaths":0,"confirmed_congenital":0,"population_x_1k":36},"bol":{"country":"Bolivia (Plurinational State of)","autochthonous_cases_suspected":718,"autochthonous_cases_confirmed":129,"imported_cases":4,"incidence_rate":7.779206465833946,"deaths":0,"confirmed_congenital":3,"population_x_1k":10888},"col":{"country":"Colombia","autochthonous_cases_suspected":96421,"autochthonous_cases_confirmed":8826,"imported_cases":0,"incidence_rate":216.3172606568833,"deaths":0,"confirmed_congenital":58,"population_x_1k":48654,"congenital_suspected":195,"congenital_probable":216,"gbs_total":417,"gbs_confirmed":0},"ecu":{"country":"Ecuador","autochthonous_cases_suspected":2722,"autochthonous_cases_confirmed":806,"imported_cases":15,"incidence_rate":21.53188892279524,"deaths":0,"confirmed_congenital":0,"population_x_1k":16385},"per":{"country":"Peru","autochthonous_cases_suspected":0,"autochthonous_cases_confirmed":143,"imported_cases":17,"incidence_rate":0.45579141964684133,"deaths":0,"confirmed_congenital":0,"population_x_1k":31374},"ven":{"country":"Venezuela (Bolivarian Republic of)","autochthonous_cases_suspected":58758,"autochthonous_cases_confirmed":2244,"imported_cases":0,"incidence_rate":193.5404042006409,"deaths":0,"confirmed_congenital":0,"population_x_1k":31519},"bra":{"country":"Brazil","autochthonous_cases_suspected":200465,"autochthonous_cases_confirmed":109596,"imported_cases":0,"incidence_rate":147.9524545732173,"deaths":6,"confirmed_congenital":2143,"population_x_1k":209568,"congenital_suspected":9289,"congenital_probable":2989},"arg":{"country":"Argentina","autochthonous_cases_suspected":1821,"autochthonous_cases_confirmed":26,"imported_cases":27,"incidence_rate":4.212374848906425,"deaths":0,"confirmed_congenital":1,"population_x_1k":43847},"chl":{"country":"Chile","autochthonous_cases_suspected":0,"autochthonous_cases_confirmed":0,"imported_cases":29,"incidence_rate":0,"deaths":0,"confirmed_congenital":0,"population_x_1k":18132},"pry":{"country":"Paraguay","autochthonous_cases_suspected":546,"autochthonous_cases_confirmed":12,"imported_cases":0,"incidence_rate":8.297397769516728,"deaths":0,"confirmed_congenital":2,"population_x_1k":6725},"ury":{"country":"Uruguay","autochthonous_cases_suspected":0,"autochthonous_cases_confirmed":0,"imported_cases":1,"incidence_rate":0,"deaths":0,"confirmed_congenital":0,"population_x_1k":344},"aia":{"country":"Anguilla","autochthonous_cases_suspected":40,"autochthonous_cases_confirmed":5,"imported_cases":1,"incidence_rate":264.70588235294116,"deaths":0,"confirmed_congenital":0,"population_x_1k":17},"atg":{"country":"Antigua and Barbuda","autochthonous_cases_suspected":393,"autochthonous_cases_confirmed":14,"imported_cases":2,"incidence_rate":432.97872340425533,"deaths":0,"confirmed_congenital":0,"population_x_1k":94},"abw":{"country":"Aruba","autochthonous_cases_suspected":614,"autochthonous_cases_confirmed":28,"imported_cases":7,"incidence_rate":563.1578947368421,"deaths":0,"confirmed_congenital":0,"population_x_1k":114,"congenital_suspected":0,"congenital_probable":0},"bhs":{"country":"Bahamas","autochthonous_cases_suspected":0,"autochthonous_cases_confirmed":21,"imported_cases":3,"incidence_rate":5.343511450381679,"deaths":0,"confirmed_congenital":0,"population_x_1k":393,"congenital_suspected":0,"congenital_probable":0,"gbs_total":0,"gbs_confirmed":0},"brb":{"country":"Barbados","autochthonous_cases_suspected":653,"autochthonous_cases_confirmed":37,"imported_cases":0,"incidence_rate":237.11340206185568,"deaths":0,"confirmed_congenital":0,"population_x_1k":291},"cym":{"country":"Cayman Islands","autochthonous_cases_suspected":201,"autochthonous_cases_confirmed":29,"imported_cases":10,"incidence_rate":403.50877192982455,"deaths":0,"confirmed_congenital":0,"population_x_1k":57},"cuw":{"country":"Curacao","autochthonous_cases_suspected":0,"autochthonous_cases_confirmed":820,"imported_cases":0,"incidence_rate":550.3355704697987,"deaths":0,"confirmed_congenital":0,"population_x_1k":149,"congenital_suspected":0,"congenital_probable":0},"dma":{"country":"Dominica","autochthonous_cases_suspected":1150,"autochthonous_cases_confirmed":79,"imported_cases":0,"incidence_rate":1660.8108108108108,"deaths":0,"confirmed_congenital":0,"population_x_1k":74},"grd":{"country":"Grenada","autochthonous_cases_suspected":314,"autochthonous_cases_confirmed":108,"imported_cases":0,"incidence_rate":380.1801801801802,"deaths":0,"confirmed_congenital":1,"population_x_1k":111,"congenital_suspected":8},"guy":{"country":"Guyana","autochthonous_cases_suspected":0,"autochthonous_cases_confirmed":6,"imported_cases":0,"incidence_rate":0.7782101167315175,"deaths":0,"confirmed_congenital":0,"population_x_1k":771},"jam":{"country":"Jamaica","autochthonous_cases_suspected":6449,"autochthonous_cases_confirmed":122,"imported_cases":0,"incidence_rate":234.42739921512666,"deaths":0,"confirmed_congenital":0,"population_x_1k":2803},"kna":{"country":"Saint Kitts and Nevis","autochthonous_cases_suspected":532,"autochthonous_cases_confirmed":26,"imported_cases":0,"incidence_rate":1073.076923076923,"deaths":0,"confirmed_congenital":0,"population_x_1k":52},"lca":{"country":"Saint Lucia","autochthonous_cases_suspected":822,"autochthonous_cases_confirmed":50,"imported_cases":0,"incidence_rate":531.7073170731708,"deaths":0,"confirmed_congenital":0,"population_x_1k":164},"vct":{"country":"Saint Vincent and the Grenadines","autochthonous_cases_suspected":156,"autochthonous_cases_confirmed":38,"imported_cases":0,"incidence_rate":190.19607843137254,"deaths":0,"confirmed_congenital":0,"population_x_1k":102},"sur":{"country":"Suriname","autochthonous_cases_suspected":2755,"autochthonous_cases_confirmed":723,"imported_cases":0,"incidence_rate":634.6715328467153,"deaths":4,"confirmed_congenital":2,"population_x_1k":548,"congenital_suspected":2,"congenital_probable":5,"gbs_total":15,"gbs_confirmed":4},"tto":{"country":"Trinidad and Tobago","autochthonous_cases_suspected":0,"autochthonous_cases_confirmed":643,"imported_cases":1,"incidence_rate":47.10622710622711,"deaths":0,"confirmed_congenital":1,"population_x_1k":1365},"tca":{"country":"Turks and Caicos Islands","autochthonous_cases_suspected":115,"autochthonous_cases_confirmed":12,"imported_cases":3,"incidence_rate":249.01960784313727,"deaths":0,"confirmed_congenital":0,"population_x_1k":51},"vgb":{"country":"Virgin Islands (UK)","autochthonous_cases_suspected":51,"autochthonous_cases_confirmed":38,"imported_cases":0,"incidence_rate":261.7647058823529,"deaths":0,"confirmed_congenital":0,"population_x_1k":34},"vir":{"country":"Virgin Islands (US)","autochthonous_cases_suspected":803,"autochthonous_cases_confirmed":462,"imported_cases":0,"incidence_rate":1228.1553398058252,"deaths":0,"confirmed_congenital":0,"population_x_1k":103}}} 2 | -------------------------------------------------------------------------------- /api/controllers/general.js: -------------------------------------------------------------------------------- 1 | import * as general_helper from '../helpers/general' 2 | import config from '../../config' 3 | import * as auth from '../helpers/auth' 4 | import qs from 'qs' 5 | // import geojson from 'geojson' 6 | import * as logger from './../helpers/logger' 7 | // convert country code 8 | import {alpha3ToAlpha2, alpha2ToAlpha3} from 'i18n-iso-countries' 9 | 10 | /** 11 | * Returns mosquito prevalence for specified country. If country is not specified it will return 12 | * mosquito prevalence for all countries. 13 | * @param {String} request - request object 14 | * @param {String} response - response object 15 | * @return {Promise} Fulfilled when records are returned 16 | */ 17 | export function getMosquito(request, response) { 18 | // let [ key, kind, country ] = request._key.split('_') 19 | 20 | let key = 'mosquito' 21 | let {kind: kind, country: country} = getParams(request) 22 | 23 | 24 | const source = config[key].source 25 | const source_url = config[key].source_url 26 | return general_helper 27 | .getMosquito(key, kind, country) 28 | .then(data => { 29 | response.json({ 30 | key: key, 31 | kind: kind, 32 | source: source, 33 | source_url: source_url, 34 | data: data 35 | }) 36 | }) 37 | .catch(err => { 38 | logger.logErrorResponse(request, err) 39 | response.json({message: err}) 40 | }) 41 | } 42 | 43 | /** 44 | * Returns population metadata available from specified source for specified country. 45 | * If country is not specified it will return data for all countries. 46 | * Default source is worldpop. 47 | * @param {String} request - request object 48 | * @param {String} response - response object 49 | * @return {Promise} Fulfilled when records are returned 50 | */ 51 | export function getPopulation(request, response) { 52 | // const [ key, source, country ] = request._key.split('_') 53 | 54 | let key = 'population' 55 | 56 | // When no source or country are specified: 'population' 57 | // If country is specified: [ worldpop, country_name ] 58 | 59 | let { 60 | source = config.population.default_source, 61 | country: country 62 | } = getParams(request) 63 | 64 | const data_source = (source !== undefined) ? source : config.population.source 65 | return general_helper 66 | .getPopulation(key, source, country) 67 | .then(data => { 68 | return response.json({ 69 | key: key, 70 | source: data_source, 71 | data: data 72 | }) 73 | }) 74 | .catch(err => { 75 | logger.logErrorResponse(request, err) 76 | response.json({message: err}) 77 | }) 78 | } 79 | 80 | 81 | /** 82 | * Returns an object with information about cases for specific kind in all countries 83 | * @param {String} request - request object 84 | * @param {String} response - response object 85 | * @return {Promise} Fulfilled when records are returned 86 | */ 87 | export function getCases(request, response) { 88 | // key represents what data we want to pull, here it is 'cases' 89 | // kind represents disease whose cases we are pulling 90 | // week-types (epi-week or iso-week) 91 | // week represents last date of epi-week. If set, the API will fetch cases only for that week 92 | 93 | // const [ key, kind, weekType, week ] = request._key.split('_') 94 | 95 | let key = 'cases' 96 | let { 97 | kind: kind, 98 | weekType: weekType, 99 | date: week 100 | } = getParams(request) 101 | 102 | const source = config.cases[kind].source 103 | const source_url = config.cases[kind].source_url 104 | 105 | return general_helper 106 | .get_cases(key, kind, weekType, week) 107 | .then(cases => { 108 | return response.json({ 109 | kind: kind, 110 | source: source, 111 | source_url: source_url, 112 | weekType: weekType, 113 | cases: cases 114 | }) 115 | }) 116 | .catch(error => { 117 | logger.logErrorResponse(request, error) 118 | response.json({message: err}) 119 | }) 120 | } 121 | 122 | /** 123 | * Returns a list of (country code, source name, shapefile set} for the given search key 124 | * @param {String} request - request object 125 | * @param {String} response - response object 126 | * @return {Promise} fulfilled when records are returned 127 | */ 128 | export function getCountriesAndSourceData(request, response) { 129 | // if the path is /v1/mobility/countries, key will be 'mobility' 130 | let key = request.swagger.apiPath.split('/')[2] 131 | return general_helper 132 | .getCountriesAndSourceData(key) // key is mobility 133 | .then(properties => { 134 | return response.json(properties) 135 | }) 136 | .catch(err => { 137 | logger.logErrorResponse(request, err) 138 | response.json({message: err}) 139 | }) 140 | } 141 | 142 | /** 143 | * Fetches schools based on country and other options specified 144 | * @param {String} request - request object 145 | * @param {String} response - response object 146 | * @return {Promise} Fullfilled when schools are returned 147 | */ 148 | export const getCountriesWithSchools = (request, response) => { 149 | const options = qs.parse(request.query); 150 | options.group_by = 'country_code'; 151 | const result = { 152 | key: 'schools', 153 | properties: [] 154 | } 155 | 156 | return new Promise((resolve, reject) => { 157 | general_helper.getCountriesWithSchools(options) 158 | .then(data => { 159 | data.rows.reduce((res, row) => { 160 | let country_code = alpha2ToAlpha3(row.country_code).toLowerCase(); 161 | res.properties.push(country_code); 162 | return res 163 | }, result) 164 | 165 | result.properties.sort() 166 | 167 | response.json(result); 168 | }) 169 | }) 170 | } 171 | 172 | /** 173 | * Returns an object with properties for specific key 174 | * @param {String} request - request object 175 | * @param {String} response - response object 176 | * @return {Promise} Fulfilled when records are returned 177 | */ 178 | export function getProperties(request, response) { 179 | // key was request._key before 180 | // key is type of data user is querying 181 | // ex: mobility, population... 182 | let key = request.swagger.apiPath.split('/')[2] 183 | let params = getParams(request) 184 | 185 | // Currently only using worldpop 186 | // so no need to specify source. 187 | // TODO: better explanation. 188 | if (key === 'population') { 189 | if (!('source' in params)) { 190 | params.source = config.population.default_source 191 | } 192 | } 193 | 194 | if (Object.keys(params).length > 0) { 195 | key += '_' + Object.keys(params).map(property => { 196 | return params[property] 197 | }).join('_') 198 | } 199 | 200 | 201 | return general_helper 202 | .getProperties(key) 203 | .then(properties => { 204 | if (properties.properties) { 205 | return response.json({ 206 | key: properties.key, 207 | properties: properties.properties 208 | }) 209 | } else { 210 | return response.json(properties); 211 | } 212 | }) 213 | .catch(err => { 214 | logger.logErrorResponse(request, err) 215 | response.json({message: err}) 216 | }) 217 | } 218 | 219 | /** 220 | * Returns a clickable link to Auth0 for users to login and get access token 221 | * @param {String} request - request object 222 | * @param {String} response - response object 223 | */ 224 | export const getToken = (request, response) => { 225 | let url = auth.getAuthorizeUrl() 226 | response.format({ 227 | 'text/html': function() { 228 | response.send('

Please click HERE ' + 231 | ' and follow next steps to get access token

') 232 | } 233 | }) 234 | } 235 | 236 | 237 | /** 238 | * Displays the token or error received from Auth0. 239 | * @param {String} request - request object 240 | * @param {String} response - response object 241 | */ 242 | export const getRefreshToken = (request, response) => { 243 | const code = request.query.code 244 | auth.getRefreshToken(code) 245 | .then(object => { 246 | response.format({ 247 | 'text/html': () => { 248 | response.json({ 249 | refresh_token: object.refresh_token 250 | }) 251 | } 252 | }) 253 | }) 254 | } 255 | /** 256 | * Displays the token or error received from Auth0. 257 | * @param {String} request - request object 258 | * @param {String} response - response object 259 | */ 260 | export const refreshToken = (request, response) => { 261 | let params = getParams(request) 262 | const refresh_token = params.refresh_token 263 | auth.refreshAccessToken(refresh_token) 264 | .then(object => { 265 | if (object.error) { 266 | return response.json(object); 267 | } 268 | response.format({ 269 | 'text/html': () => { 270 | response.json({ 271 | access_token: object.access_token 272 | }) 273 | } 274 | }) 275 | }) 276 | } 277 | /** 278 | * Displays the token or error received from Auth0. 279 | * @param {String} request - request object 280 | * @param {String} response - response object 281 | */ 282 | export const showToken = (request, response) => { 283 | let authObject = qs.parse(request.body) 284 | let token = (authObject.error) ? authObject.error_description : 'Token:' + 285 | authObject.access_token 286 | response.format({ 287 | 'text/html': function() { 288 | response.send('

' + token + '

') 289 | } 290 | }) 291 | } 292 | 293 | /** 294 | * Displays the token or error received from Auth0. 295 | * @param {String} request - request object 296 | * @param {String} response - response object 297 | */ 298 | export const getSchools = (request, response) => { 299 | let { 300 | country: country 301 | } = getParams(request) 302 | const options = qs.parse(request.query) 303 | 304 | country = alpha3ToAlpha2(country.toUpperCase()) 305 | 306 | general_helper 307 | .getSchools(country, options) 308 | .then(result => { 309 | let csv_like_array = [Object.keys(result.rows[0])] 310 | result.rows.forEach((e, i) => { 311 | csv_like_array.push(Object.values(e)) 312 | }); 313 | response.json({ 314 | count: result.count, 315 | result: csv_like_array, 316 | // result: geojson.parse(result.rows, {Point: ['lat', 'lon']}), 317 | hasNext: result.hasNext 318 | }) 319 | }) 320 | .catch(err => { 321 | logger.logErrorResponse(request, err) 322 | response.json({message: err}) 323 | }) 324 | } 325 | 326 | /** 327 | * getSchool - gets a school 328 | * @param {String} request request 329 | * @param {String} response response 330 | */ 331 | export const getSchool = (request, response) => { 332 | const { 333 | school_id: school_id 334 | } = getParams(request) 335 | const options = qs.parse(request.query) 336 | general_helper 337 | .getSchool(school_id, options) 338 | .then(result => { 339 | console.log(result); 340 | let csv_like_array = [Object.keys(result.rows[0])] 341 | result.rows.forEach((e, i) => { 342 | csv_like_array.push(Object.values(e)) 343 | }); 344 | response.json({ 345 | count: result.count, 346 | result: csv_like_array, 347 | // result: geojson.parse(result.rows, {Point: ['lat', 'lon']}), 348 | hasNext: result.hasNext 349 | }) 350 | }) 351 | .catch(err => { 352 | logger.logErrorResponse(request, err) 353 | response.json({message: err}) 354 | }) 355 | } 356 | 357 | /** 358 | * Returns object with all request parameters 359 | * @param {String} request - request object 360 | * @return {object} params all request parameters 361 | */ 362 | export const getParams = (request) => { 363 | let params = {} 364 | Object.keys(request.swagger.params).forEach(property => { 365 | params[property] = request.swagger.params[property].value 366 | }) 367 | return params 368 | } 369 | -------------------------------------------------------------------------------- /api/helpers/general.js: -------------------------------------------------------------------------------- 1 | import config from '../../config' 2 | import bluebird from 'bluebird' 3 | import PostgresHelper from './postgres' 4 | import * as data_access from '../../utils/data_access' 5 | import fs from 'fs' 6 | const dbClient = new PostgresHelper() 7 | 8 | /** 9 | * Returns a list of (country code, source name, shapefile set} for the given search key 10 | * @param {String} key - the search key 11 | * @return {Promise} fulfilled when records are returned 12 | */ 13 | export function getCountriesAndSourceData(key) { 14 | return new Promise((resolve, reject) => { 15 | switch (key) { 16 | case 'mobility': { 17 | let path = './public/aggregations/' + key + '/' 18 | let filenames = walkSync(path, []) 19 | let results = createArrayOfCountries(filenames) 20 | return resolve(results) 21 | } 22 | } 23 | } 24 | ) 25 | } 26 | 27 | /** 28 | * Transforms a list of filenames into an array of objects in desired format 29 | * @param {Array} filenames - list of filenames to be transformed 30 | * @return {Array} unique_results - array of unique objects in our desired format 31 | */ 32 | function createArrayOfCountries(filenames) { 33 | let results = filenames 34 | .filter(line => { 35 | return line.match(/csv/i) 36 | }) 37 | .map(line => { 38 | return line.split('/').slice(0, 3) // discard csv in path 39 | }).reduce((arr, line) => { 40 | let obj = { 41 | 'country': line[2], 'source': line[0], 'series': line[1], 42 | // rebuild path in case apps need it later 43 | 'path': `/mobility/sources/${line[0]}/series/${line[1]}/countries/${line[2]}`, 44 | 'version': '/api/v1' 45 | } 46 | arr.push(obj) 47 | return arr 48 | }, []) 49 | // de-duplicate values in the array 50 | let unique_results = Object.values(results.reduce((h, e) => { 51 | h[e.country + e.source + e.shapefile] = e 52 | return h 53 | }, {})) 54 | return unique_results 55 | } 56 | 57 | /** 58 | * Traverses a directory recursively and returns the full pathnames of the files 59 | * at the deepest level 60 | * @param {String} dir path for the base directory 61 | * @param {Array} filelist list of filenames to be returned 62 | * @return {Array} list of filenames at the deepest level 63 | */ 64 | function walkSync(dir, filelist) { 65 | let files = fs.readdirSync(dir) 66 | filelist = filelist || []; 67 | files.forEach(file => { 68 | if (fs.statSync(dir + file).isDirectory()) { 69 | filelist = walkSync(dir + file + '/', filelist) 70 | } else { 71 | filelist.push(dir.split('/').slice(4).join('/') + file) 72 | } 73 | }) 74 | 75 | return filelist 76 | } 77 | 78 | /** 79 | * Returns name of the a country's shapefile from` the List of shapefiles 80 | * @param {List} shapefiles List of shapefiles 81 | * @param {String} country Name of the country 82 | * @return {String} fileName Name of the shapefile for specified country, empty string if not found 83 | */ 84 | const getFile = (shapefiles, country) => { 85 | let fileName = '' 86 | let files = shapefiles.filter(shapefile => { 87 | fileName = shapefile.split('/')[1] 88 | return fileName.split('_')[0] === country 89 | }) 90 | if (files.length > 0) { 91 | fileName = files[0] 92 | } 93 | return fileName 94 | } 95 | 96 | /** 97 | * Return an object with list of countries and aggregated data for each country 98 | * @param {String} kind - Type of data requested (population or mosquito) 99 | * @param {String} source - source from which data should be fetched 100 | * @param {String} country - country for which data should be fetched, 101 | * if not specified fetch data for all countries 102 | * @return{Promise} Fulfilled when records are returned 103 | */ 104 | export const countries_with_this_kind_data = (kind, source, country) => { 105 | return new Promise((resolve, reject) => { 106 | // Get country name for each shapefile 107 | getShapeFiles(kind, source) 108 | .then(shapefileSet => { 109 | if (country) { 110 | let fileName = getFile(shapefileSet.shapefiles, country) 111 | if (fileName.length > 0) { 112 | shapefileSet.country = country 113 | shapefileSet.fileName = fileName 114 | shapefileSet.shapefiles = [] 115 | return getGeoProperties(shapefileSet) 116 | .then(readShapeFile) 117 | .then(mergePropertiesWithShapefile) 118 | } else { 119 | return reject('country not found') 120 | } 121 | } else { 122 | return aggregateShapeFiles(shapefileSet) 123 | } 124 | }) 125 | .then(population => { 126 | return resolve(population) 127 | }) 128 | }) 129 | } 130 | 131 | 132 | /** 133 | * Returns geo-properties for a country 134 | * @param {object} shapefileSet Object with information regarding requested data 135 | * e.g. country, name of shapefile etc. 136 | * @return {Promise} Fulfilled when geo-properties are returned 137 | */ 138 | const getGeoProperties = (shapefileSet) => { 139 | return new Promise((resolve, reject) => { 140 | let fileName = shapefileSet.fileName 141 | let geo_props_file_name = fileName.match(/[a-z]{3}_\d/)[0].toUpperCase() + 142 | '.json' 143 | data_access.read_file('geo-properties', 'gadm2-8', geo_props_file_name) 144 | .then(admin_properties => { 145 | shapefileSet.admin_properties = admin_properties 146 | return resolve(shapefileSet) 147 | }) 148 | .catch(error => { 149 | return reject( 150 | 'Error getting geo-properties for ' + shapefileSet.country 151 | ) 152 | }) 153 | }) 154 | } 155 | 156 | /** 157 | * Returns list of shapefiles from specified source for specified kind of data 158 | * @param {String} kind - Type of data requested (population or mosquito) 159 | * @param {String} source - source from which data should be fetched 160 | * @return {Promise} Fulfilled when shapefiles are returned 161 | */ 162 | export const getShapeFiles = (kind, source) => { 163 | return new Promise((resolve, reject) => { 164 | data_access.get_file_list(kind, source) 165 | .then(directories => { 166 | let dirs_shapefiles = extract_dirs(directories.entries.directories) 167 | let shapefiles = [] 168 | bluebird.each(dirs_shapefiles, directory => { 169 | return data_access.get_file_list(kind, source + '/' + directory) 170 | .then(fileList => { 171 | fileList.entries.files.forEach(file => { 172 | shapefiles.push(directory + '/' + file.name) 173 | }) 174 | }) 175 | }, { 176 | concurrency: 1 177 | }) 178 | .then(() => { 179 | resolve({ 180 | kind, 181 | source, 182 | shapefiles 183 | }) 184 | }) 185 | }) 186 | }) 187 | } 188 | 189 | /** 190 | * Extracts and returns data from shapefiles 191 | * @param {object} shapefileSet Object with information regarding requested data 192 | * e.g. country, name of shapefile etc. 193 | * @return {Promise} Fulfilled when aggregated data is returned 194 | */ 195 | export const aggregateShapeFiles = (shapefileSet) => { 196 | return new Promise((resolve, reject) => { 197 | let population = {} 198 | shapefileSet.shapefiles.forEach(shapefile => { 199 | const fileName = shapefile.split('/')[1] 200 | const record = file_to_record(fileName); 201 | if (population[record.country]) { 202 | population[record.country].push(record); 203 | } else { 204 | population[record.country] = [record]; 205 | } 206 | }); 207 | return resolve(population); 208 | }) 209 | } 210 | 211 | 212 | /** 213 | * Reads and returns content of a shapefile 214 | * @param {object} shapefileSet Object with information regarding requested data 215 | * e.g. country, name of shapefile etc. 216 | * @return {Promise} Fulfilled when a shapefile is read 217 | */ 218 | const readShapeFile = (shapefileSet) => { 219 | return new Promise((resolve, reject) => { 220 | let { 221 | kind, 222 | source, 223 | fileName 224 | } = shapefileSet 225 | const [database, file] = fileName.split('/') 226 | data_access.read_file(kind, source + '/' + database, file) 227 | .then(content => { 228 | shapefileSet.shapefile = content 229 | resolve(shapefileSet) 230 | }) 231 | }); 232 | } 233 | 234 | 235 | /** 236 | * This function will merge geo-properties with shapefile content 237 | * @param {object} shapefileSet Object with information regarding requested data 238 | * e.g. country, name of shapefile etc. 239 | * @return {Promise} Fulfilled when geo-properties are merged with shapefile content 240 | */ 241 | const mergePropertiesWithShapefile = (shapefileSet) => { 242 | return new Promise((resolve, reject) => { 243 | let { 244 | kind, 245 | source: dir, 246 | fileName, 247 | country, 248 | admin_properties, 249 | shapefile 250 | } = shapefileSet 251 | let [raster, source] = fileName.split('^').slice(1, 3) 252 | let admin_to_value_map = {} 253 | admin_to_value_map.raster = raster 254 | // source refers to raster source 255 | admin_to_value_map.source = source 256 | admin_to_value_map.values = {} 257 | let value_map = shapefile.reduce((ary, element) => { 258 | let tempList = Object.keys(element).filter(key => { 259 | return (key.startsWith('id_')) 260 | }).map(key => { 261 | return element[key] 262 | }) 263 | let temp_map = {} 264 | // config[kind].val_type is sum or mean 265 | // element[config[kind].val_type] is a floating point 266 | temp_map[country + '_' + 267 | tempList.join('_') + '_' + 268 | dir] = element[config[kind].val_type] 269 | 270 | // Enrich each object with the feature properties from the original shapefile 271 | let admin_props = assign_correct_admin_from_admins( 272 | admin_properties, 273 | tempList 274 | ); 275 | ary.push( 276 | Object.assign({ 277 | admin_id: country + 278 | '_' + 279 | tempList.join('_') + 280 | '_' + 281 | // gadm2-8 or santiblanko: gadm2-8/afg_2_gadm2-8^popmap15adj^worldpop^42348516^248596^641869.188.json 282 | // a bit of a hack to put the shapefile source in the id 283 | fileName.split('/')[0], 284 | value: element[config[kind].val_type] 285 | }, 286 | admin_props 287 | ) 288 | ) 289 | return ary 290 | }, []); 291 | admin_to_value_map.values = value_map; 292 | resolve(admin_to_value_map) 293 | }); 294 | } 295 | 296 | /** 297 | * Return object for raster that contains metadata gleaned from the raster file name 298 | * @param {Object} file_obj - raster blob object from storage 299 | * @return {Object} Raster metadata 300 | */ 301 | function file_to_record(file_obj) { 302 | let [ary, raster, data_source, sum, sq_km] = file_obj.split(/\^/); 303 | let [country, admin_level, shapefile] = ary.split('_') 304 | sum = parseFloat(sum); 305 | sq_km = parseInt(sq_km.replace(/.json/, '')); 306 | raster = raster.replace(/.json$/, '') 307 | let density = (sum / sq_km) 308 | 309 | return { 310 | country, 311 | data_source, 312 | shapefile, 313 | admin_level, 314 | sum, 315 | sq_km, 316 | density, 317 | raster 318 | } 319 | } 320 | 321 | 322 | /** 323 | * Returns directory names from an array of directory properties 324 | * @param {List} ary Array of directory properties 325 | * @return {List} List of names of directories 326 | */ 327 | function extract_dirs(ary) { 328 | return ary.map(e => { 329 | return e.name; 330 | }); 331 | } 332 | 333 | 334 | /** 335 | * Returns files having case data of specified kind 336 | * @param {String} key Type of data requested (cases) 337 | * @param {String} kind Name of the epidemic 338 | * @param {String} weekType Week type (epi or iso) 339 | * @param {String} week first day of week, if not specified it will fetch data for all the weeks 340 | * @return {Promise} Fulfilled when case files are returned 341 | */ 342 | export const getCaseFiles = (key, kind, weekType, week) => { 343 | return new Promise((resolve, reject) => { 344 | let casesPath = config[key][kind].path + '/' + weekType 345 | data_access.get_file_list(key, casesPath) 346 | .then(files => { 347 | files = files.entries.files 348 | if (week !== undefined) { 349 | files = files.filter(file => { 350 | return file.name.replace(/.json/g, '') === week; 351 | }); 352 | if (files.length !== 1) { 353 | console.error('Error -> File not found', week); 354 | return reject() 355 | } 356 | } 357 | return resolve({ 358 | key, 359 | kind, 360 | weekType, 361 | files 362 | }) 363 | }) 364 | }) 365 | } 366 | 367 | 368 | /** 369 | * Returns case data from the files 370 | * @param {object} caseFiles Object with information regarding requested data 371 | * e.g. kind, list of case files etc. 372 | * @return {Promise} Fulfilled when case data is read and returned 373 | */ 374 | export const readCaseFiles = (caseFiles) => { 375 | return new Promise((resolve, reject) => { 376 | let returnObj = {} 377 | let { 378 | key: key, 379 | kind: kind, 380 | weekType: weekType 381 | } = caseFiles 382 | bluebird.each(caseFiles.files, file => { 383 | let objKey = file.name.replace(/.json/g, ''); 384 | let filePath = config[key][kind].path + '/' + weekType 385 | return data_access.read_file(key, filePath, file.name) 386 | .then(content => { 387 | returnObj[objKey] = content.countries 388 | }) 389 | .catch(error => { 390 | console.log('Error', error) 391 | }); 392 | }, { 393 | concurrency: 1 394 | }) 395 | .then(() => { 396 | return resolve(returnObj) 397 | }) 398 | }) 399 | } 400 | 401 | 402 | /** 403 | * Fetches cases of specified kind for specified week. To get all the cases set week to null 404 | * @param {String} key Key for azure_helper (should always be cases) 405 | * @param {String} kind name of disease 406 | * @param {String} weekType type of week (epi or iso) 407 | * @param {String} week Last day of the week 408 | * @return {Promise} Fulfilled when records are returned 409 | */ 410 | export const get_cases = (key, kind, weekType, week) => { 411 | return new Promise((resolve, reject) => { 412 | getCaseFiles(key, kind, weekType, week) 413 | .then(readCaseFiles).catch(reject) 414 | .then(cases => { 415 | resolve(cases) 416 | }) 417 | .catch(reject) 418 | }); 419 | } 420 | 421 | 422 | /** 423 | * Returns list of properties for given query 424 | * @param {String} queryString string specifing key for properties 425 | * @return {Promise} Fulfilled when records are returned 426 | */ 427 | export const getProperties = (queryString) => { 428 | return new Promise((resolve, reject) => { 429 | let queryParts = queryString.split('_') 430 | let key = queryParts[0] 431 | let path = '' 432 | switch (key) { 433 | case 'population': 434 | { 435 | if (queryParts.length === 2) { 436 | if (queryParts[1] === 'worldpop') { 437 | path += 'worldpop/' + config.population.default_database 438 | } else { 439 | data_access.read_file(key, 'worldbank', 'population.json') 440 | .then(content => { 441 | let properties = { 442 | key: queryParts.join('_'), 443 | properties: Object.keys(content) 444 | } 445 | return resolve(properties) 446 | }) 447 | break; 448 | } 449 | } 450 | fetchProperty(key, path, '_', 0) 451 | .then(propertyList => { 452 | let properties = { 453 | key: queryParts.join('_'), 454 | properties: propertyList 455 | } 456 | return resolve(properties) 457 | }) 458 | break; 459 | } 460 | case 'mobility': 461 | { 462 | if (queryParts.find(e => { 463 | return e.match(/\.csv$/) 464 | })) { 465 | let file = queryParts.pop(); 466 | path = queryParts.slice(1).join('/') 467 | return resolve(data_access.read_file(key, path, file)) 468 | } 469 | path = queryParts.slice(1).join('/') 470 | fetchProperty(key, path, '_', 0) 471 | .then(propertyList => { 472 | let properties = { 473 | key: queryParts.join('_'), 474 | properties: propertyList 475 | } 476 | return resolve(properties) 477 | }) 478 | break 479 | } 480 | case 'mosquito': 481 | { 482 | if (queryParts.length === 2) { 483 | path += queryParts[1] + '/' + 484 | config.mosquito.default_source + '/' + 485 | config.mosquito.default_database 486 | } 487 | fetchProperty(key, path, '_', 0) 488 | .then(propertyList => { 489 | let properties = { 490 | key: queryParts.join('_'), 491 | properties: propertyList 492 | } 493 | return resolve(properties) 494 | }) 495 | break 496 | } 497 | case 'cases': 498 | { 499 | if (queryParts.length > 1) { 500 | path += config.cases[queryParts[1]].path + 501 | queryParts.slice(2).join('/') 502 | } 503 | fetchProperty(key, path, '.', 0) 504 | .then(propertyList => { 505 | let properties = { 506 | key: queryParts.join('_'), 507 | properties: propertyList 508 | } 509 | return resolve(properties) 510 | }) 511 | break 512 | } 513 | } 514 | }) 515 | } 516 | 517 | 518 | /** 519 | * Reads directory specified by key and path and returns list of properties 520 | * @param {String} key Key of the requested data 521 | * @param {String} path Path of the resource 522 | * @param {String} splitOn string specified part of the properties to ignor (e.g. '.json' in case of JSON files) 523 | * @param {int} part specifies which part to select after spliting the property string 524 | * @return {Promise} Fulfilled when records are returned 525 | */ 526 | const fetchProperty = (key, path, splitOn, part) => { 527 | return new Promise((resolve, reject) => { 528 | data_access.get_file_list(key, path) 529 | .then(fileList => { 530 | let propertyList = fileList.entries.directories.length > 0 ? 531 | fileList.entries.directories : fileList.entries.files; 532 | propertyList = propertyList.reduce((list, element) => { 533 | list.push(element.name.split(splitOn)[part]) 534 | return list 535 | }, []) 536 | return resolve(propertyList) 537 | }) 538 | }); 539 | } 540 | 541 | 542 | /** 543 | * Returns population metadata available from specified source for specified country. 544 | * If country is not specified it will return data for all countries. 545 | * @param {String} key key 546 | * @param {String} source Source for the population data 547 | * @param {String} country country for which we need the data 548 | * @return {Promise} Fulfilled when records are returned 549 | */ 550 | export const getPopulation = (key, source, country) => { 551 | return new Promise((resolve, reject) => { 552 | source = (source !== undefined) ? source : config[key].default_source 553 | switch (source) { 554 | case 'worldpop': 555 | { 556 | countries_with_this_kind_data(key, source, country) 557 | .then(data => { 558 | return resolve(data) 559 | }) 560 | .catch(reject) 561 | break 562 | } 563 | } 564 | }); 565 | } 566 | 567 | /** 568 | * Returns mosquito metadata available from specified source for specified country. 569 | * If country is not specified it will return data for all countries. 570 | * @param {String} key key 571 | * @param {String} kind Source for the mosquito data 572 | * @param {String} country country for which we need the data 573 | * @return {Promise} Fulfilled when records are returned 574 | */ 575 | export const getMosquito = (key, kind, country) => { 576 | return new Promise((resolve, reject) => { 577 | countries_with_this_kind_data(key, kind + 578 | '/' + config.mosquito.default_source, country) 579 | .then(data => { 580 | return resolve(data) 581 | }) 582 | .catch(error => { 583 | return reject(error) 584 | }) 585 | }); 586 | } 587 | 588 | 589 | /** 590 | * Return admin properties that matches spark output ids 591 | * @param {Array} admin_properties_ary admin properties per a country 592 | * @param {Array} spark_output_ids ids from spark aggregation output 593 | * @return{Promise} Fullfilled Admin poperties obj 594 | */ 595 | function assign_correct_admin_from_admins( 596 | admin_properties_ary, spark_output_ids 597 | ) { 598 | let index_short_cut = parseInt( 599 | spark_output_ids[spark_output_ids.length - 1] 600 | ) - 1; 601 | return admin_properties_ary.slice(index_short_cut).find(p => { 602 | let count = 0; 603 | const temp_admin_id = Object.keys(p).reduce((ary, k) => { 604 | if (k == 'ID_' + count) { 605 | ary.push(p[k]) 606 | count += 1; 607 | } 608 | return ary; 609 | }, []) 610 | 611 | return temp_admin_id.join('_') === spark_output_ids.join('_'); 612 | }) 613 | } 614 | 615 | /** 616 | * Fetches schools based on country and other options specified 617 | * @param {object} options other options as connectivity, environment, water etc. 618 | * to limit number of schools use option max_limit 619 | * @return{Promise} Fullfilled when schools are returned 620 | */ 621 | export const getCountriesWithSchools = (options) => { 622 | return new Promise((resolve, reject) => { 623 | let select = 'SELECT country_code FROM schools' 624 | dbClient.execute(select, options) 625 | .then(resolve) 626 | .catch(reject) 627 | }) 628 | } 629 | 630 | /** 631 | * Fetches schools based on country and other options specified 632 | * @param {string} country country code 633 | * @param {object} options other options as connectivity, environment, water etc. 634 | * to limit number of schools use option max_limit 635 | * @return{Promise} Fullfilled when schools are returned 636 | */ 637 | export const getSchools = (country, options) => { 638 | return new Promise((resolve, reject) => { 639 | let select = 'SELECT id, lat, lon, speed_connectivity, ' + 640 | 'type_connectivity FROM schools' 641 | options.country_code = country 642 | // let select = 'SELECT * from home_temp' 643 | // options.dept = country 644 | dbClient.execute(select, options) 645 | .then(resolve) 646 | .catch(reject) 647 | }) 648 | } 649 | 650 | 651 | /** 652 | * getSchool - Description 653 | * 654 | * @param {type} id school id 655 | * @param {type} options schools options 656 | * 657 | * @return {type} promise 658 | */ 659 | export const getSchool = (id, options) => { 660 | return new Promise((resolve, reject) => { 661 | console.log('inGS1'); 662 | console.log(id); 663 | let select = 'SELECT address, admin0, admin1, admin2, admin3, admin4, ' + 664 | 'admin_code, admin_id, altitude, availability_connectivity, ' + 665 | 'connectivity, country_code, datasource, description, educ_level, ' + 666 | 'electricity, environment, frequency, latency_connectivity, lat, ' + 667 | 'lon, name, num_classrooms, num_latrines, num_teachers, num_students, ' + 668 | 'num_sections, phone_number, postal_code, speed_connectivity, ' + 669 | 'type_connectivity, type_school, water, created_at, updated_at, ' + 670 | 'probe_id, probe_provider, isp_id, school_id, ' + 671 | 'id_0, id_1, id_2, id_3, id_4, id_5 FROM schools'; 672 | options.id = id 673 | dbClient.execute(select, options) 674 | .then(resolve) 675 | .catch(reject) 676 | }) 677 | } 678 | -------------------------------------------------------------------------------- /public/aggregations/population/worldpop/gadm2-8/afg_2_gadm2-8^popmap15adj^worldpop^42348516^248596^641869.188.json: -------------------------------------------------------------------------------- 1 | [{"gid":129,"kilometers":803.630066198859,"id_0":"1","id_1":"12","id_2":"129","sum":74459.9892636929},{"gid":195,"kilometers":88.9560273953211,"id_0":"1","id_1":"18","id_2":"195","sum":52400.409280479},{"gid":251,"kilometers":275.673298009373,"id_0":"1","id_1":"25","id_2":"251","sum":40272.116154395},{"gid":106,"kilometers":780.301645621907,"id_0":"1","id_1":"11","id_2":"106","sum":68960.0826659426},{"gid":120,"kilometers":13.2729650668111,"id_0":"1","id_1":"12","id_2":"120","sum":263339.691302001},{"gid":285,"kilometers":424.275603063926,"id_0":"1","id_1":"29","id_2":"285","sum":71381.905415169},{"gid":8,"kilometers":1744.45893028534,"id_0":"1","id_1":"1","id_2":"8","sum":138573.192714578},{"gid":264,"kilometers":190.264587268741,"id_0":"1","id_1":"26","id_2":"264","sum":77163.1390270349},{"gid":305,"kilometers":447.418829006148,"id_0":"1","id_1":"31","id_2":"305","sum":219522.378564382},{"gid":80,"kilometers":626.896026300101,"id_0":"1","id_1":"9","id_2":"80","sum":56364.6073107943},{"gid":318,"kilometers":271.264572800321,"id_0":"1","id_1":"33","id_2":"318","sum":123053.021876462},{"gid":312,"kilometers":488.873688079503,"id_0":"1","id_1":"33","id_2":"312","sum":122280.123563595},{"gid":179,"kilometers":63.9356896573351,"id_0":"1","id_1":"17","id_2":"179","sum":23623.7324189767},{"gid":209,"kilometers":313.391279575036,"id_0":"1","id_1":"21","id_2":"209","sum":110623.250927843},{"gid":276,"kilometers":525.701334633467,"id_0":"1","id_1":"28","id_2":"276","sum":182255.599671036},{"gid":110,"kilometers":274.859596134037,"id_0":"1","id_1":"11","id_2":"110","sum":104964.425508678},{"gid":175,"kilometers":165.660733613924,"id_0":"1","id_1":"17","id_2":"175","sum":179052.999010727},{"gid":99,"kilometers":1090.1863394003,"id_0":"1","id_1":"10","id_2":"99","sum":111292.356179338},{"gid":48,"kilometers":767.354086995045,"id_0":"1","id_1":"5","id_2":"48","sum":113914.769652634},{"gid":28,"kilometers":472.716068907535,"id_0":"1","id_1":"3","id_2":"28","sum":57926.1141054193},{"gid":94,"kilometers":4276.6738870403,"id_0":"1","id_1":"10","id_2":"94","sum":298737.157268831},{"gid":262,"kilometers":277.050875893368,"id_0":"1","id_1":"26","id_2":"262","sum":127108.207637139},{"gid":313,"kilometers":363.619932599003,"id_0":"1","id_1":"33","id_2":"313","sum":50842.4020610303},{"gid":204,"kilometers":216.298231103239,"id_0":"1","id_1":"20","id_2":"204","sum":106026.543024965},{"gid":151,"kilometers":133.771650511014,"id_0":"1","id_1":"14","id_2":"151","sum":187304.362242941},{"gid":162,"kilometers":5612.96407206531,"id_0":"1","id_1":"15","id_2":"162","sum":22164.8547770698},{"gid":30,"kilometers":642.827822796675,"id_0":"1","id_1":"3","id_2":"30","sum":152026.747396262},{"gid":253,"kilometers":382.924859130722,"id_0":"1","id_1":"25","id_2":"253","sum":91091.2625239193},{"gid":119,"kilometers":673.290176683657,"id_0":"1","id_1":"12","id_2":"119","sum":193991.540763808},{"gid":299,"kilometers":246.175426835359,"id_0":"1","id_1":"31","id_2":"299","sum":77684.17185992},{"gid":95,"kilometers":1265.21848664937,"id_0":"1","id_1":"10","id_2":"95","sum":145750.163700446},{"gid":270,"kilometers":597.362088285564,"id_0":"1","id_1":"26","id_2":"270","sum":173109.791907981},{"gid":122,"kilometers":748.615587019429,"id_0":"1","id_1":"12","id_2":"122","sum":116518.101219498},{"gid":62,"kilometers":2489.19048642287,"id_0":"1","id_1":"7","id_2":"62","sum":52691.8090550881},{"gid":268,"kilometers":43.290846044363,"id_0":"1","id_1":"26","id_2":"268","sum":14236.1400258765},{"gid":214,"kilometers":59.609538699944,"id_0":"1","id_1":"22","id_2":"214","sum":113133.023804128},{"gid":310,"kilometers":778.811594485114,"id_0":"1","id_1":"32","id_2":"310","sum":90181.1923093535},{"gid":180,"kilometers":141.272839518683,"id_0":"1","id_1":"17","id_2":"180","sum":106877.349964533},{"gid":127,"kilometers":680.21472678846,"id_0":"1","id_1":"12","id_2":"127","sum":140124.404723182},{"gid":101,"kilometers":1498.82505960676,"id_0":"1","id_1":"11","id_2":"101","sum":137303.337937826},{"gid":20,"kilometers":410.269311270445,"id_0":"1","id_1":"2","id_2":"20","sum":99714.9815713335},{"gid":82,"kilometers":250.411992554445,"id_0":"1","id_1":"9","id_2":"82","sum":71853.7526044846},{"gid":25,"kilometers":522.765048089007,"id_0":"1","id_1":"3","id_2":"25","sum":84493.799369365},{"gid":26,"kilometers":671.91726168243,"id_0":"1","id_1":"3","id_2":"26","sum":110112.108549349},{"gid":213,"kilometers":184.825276934487,"id_0":"1","id_1":"22","id_2":"213","sum":162614.988598168},{"gid":137,"kilometers":493.089365715389,"id_0":"1","id_1":"13","id_2":"137","sum":40787.5648812987},{"gid":117,"kilometers":2479.35215715068,"id_0":"1","id_1":"12","id_2":"117","sum":111418.1775034},{"gid":185,"kilometers":251.458234253374,"id_0":"1","id_1":"18","id_2":"185","sum":61850.5760845281},{"gid":265,"kilometers":136.250402795582,"id_0":"1","id_1":"26","id_2":"265","sum":93512.2697311006},{"gid":292,"kilometers":494.943683933709,"id_0":"1","id_1":"30","id_2":"292","sum":94533.7667264426},{"gid":168,"kilometers":68.5463301147509,"id_0":"1","id_1":"16","id_2":"168","sum":172058.512558253},{"gid":136,"kilometers":381.533773748992,"id_0":"1","id_1":"13","id_2":"136","sum":72712.2539193872},{"gid":196,"kilometers":286.082079926559,"id_0":"1","id_1":"19","id_2":"196","sum":129008.533249455},{"gid":218,"kilometers":140.69525942264,"id_0":"1","id_1":"22","id_2":"218","sum":54492.4527045861},{"gid":309,"kilometers":584.172542793481,"id_0":"1","id_1":"32","id_2":"309","sum":38565.8726927824},{"gid":238,"kilometers":722.19671311811,"id_0":"1","id_1":"24","id_2":"238","sum":26322.0124861307},{"gid":27,"kilometers":1369.68943145958,"id_0":"1","id_1":"3","id_2":"27","sum":113810.260632467},{"gid":286,"kilometers":937.118842010791,"id_0":"1","id_1":"29","id_2":"286","sum":95323.4744288847},{"gid":230,"kilometers":215.1102667214,"id_0":"1","id_1":"22","id_2":"230","sum":123492.620027632},{"gid":155,"kilometers":1703.80988971031,"id_0":"1","id_1":"15","id_2":"155","sum":171511.545472741},{"gid":149,"kilometers":162.772625224264,"id_0":"1","id_1":"14","id_2":"149","sum":215670.446237341},{"gid":93,"kilometers":101.552987508959,"id_0":"1","id_1":"9","id_2":"93","sum":20489.6318903267},{"gid":307,"kilometers":747.729708152773,"id_0":"1","id_1":"32","id_2":"307","sum":99808.6154122315},{"gid":97,"kilometers":1292.25377934809,"id_0":"1","id_1":"10","id_2":"97","sum":66511.6837490182},{"gid":205,"kilometers":337.195385976917,"id_0":"1","id_1":"20","id_2":"205","sum":86251.3691970725},{"gid":197,"kilometers":488.40049671053,"id_0":"1","id_1":"19","id_2":"197","sum":146469.854912978},{"gid":187,"kilometers":101.280472077285,"id_0":"1","id_1":"18","id_2":"187","sum":69573.4494172856},{"gid":114,"kilometers":4282.84843368105,"id_0":"1","id_1":"12","id_2":"114","sum":137784.294445235},{"gid":191,"kilometers":56.5364329621794,"id_0":"1","id_1":"18","id_2":"191","sum":34135.0899071395},{"gid":156,"kilometers":553.057957590515,"id_0":"1","id_1":"15","id_2":"156","sum":20507.8203065991},{"gid":295,"kilometers":256.991160818164,"id_0":"1","id_1":"31","id_2":"295","sum":103387.628113672},{"gid":11,"kilometers":1732.03561227776,"id_0":"1","id_1":"1","id_2":"11","sum":104625.427189234},{"gid":302,"kilometers":623.085136077794,"id_0":"1","id_1":"31","id_2":"302","sum":210020.142350108},{"gid":249,"kilometers":59.4443659906173,"id_0":"1","id_1":"25","id_2":"249","sum":22509.7653289884},{"gid":269,"kilometers":45.7579705662129,"id_0":"1","id_1":"26","id_2":"269","sum":10638.4341614991},{"gid":126,"kilometers":963.754671196585,"id_0":"1","id_1":"12","id_2":"126","sum":110181.873307711},{"gid":135,"kilometers":328.923054762575,"id_0":"1","id_1":"13","id_2":"135","sum":83860.9816799425},{"gid":194,"kilometers":142.136341307294,"id_0":"1","id_1":"18","id_2":"194","sum":59645.8234170079},{"gid":228,"kilometers":123.045917984758,"id_0":"1","id_1":"22","id_2":"228","sum":51325.2733441666},{"gid":252,"kilometers":144.391345848882,"id_0":"1","id_1":"25","id_2":"252","sum":22337.2975804694},{"gid":39,"kilometers":333.463126869507,"id_0":"1","id_1":"4","id_2":"39","sum":84068.2085493207},{"gid":316,"kilometers":965.170732329231,"id_0":"1","id_1":"33","id_2":"316","sum":140254.559012262},{"gid":132,"kilometers":370.68185689015,"id_0":"1","id_1":"13","id_2":"132","sum":102534.180164646},{"gid":131,"kilometers":516.497211319805,"id_0":"1","id_1":"13","id_2":"131","sum":93006.6680727862},{"gid":67,"kilometers":695.782538477548,"id_0":"1","id_1":"8","id_2":"67","sum":94723.6950517744},{"gid":178,"kilometers":183.112570172495,"id_0":"1","id_1":"17","id_2":"178","sum":90892.2915140502},{"gid":255,"kilometers":1233.39033665643,"id_0":"1","id_1":"25","id_2":"255","sum":37403.1375324579},{"gid":241,"kilometers":441.463743962843,"id_0":"1","id_1":"24","id_2":"241","sum":73550.9588893754},{"gid":17,"kilometers":217.747625514995,"id_0":"1","id_1":"2","id_2":"17","sum":14933.8338195682},{"gid":325,"kilometers":578.235407371319,"id_0":"1","id_1":"34","id_2":"325","sum":80928.7132066712},{"gid":66,"kilometers":504.026030667192,"id_0":"1","id_1":"7","id_2":"66","sum":16575.7160251364},{"gid":142,"kilometers":47.3162834762632,"id_0":"1","id_1":"14","id_2":"142","sum":66194.8178991303},{"gid":224,"kilometers":138.871955015933,"id_0":"1","id_1":"22","id_2":"224","sum":106724.647860572},{"gid":89,"kilometers":319.158903315955,"id_0":"1","id_1":"9","id_2":"89","sum":77688.1181613691},{"gid":160,"kilometers":1099.16637486725,"id_0":"1","id_1":"15","id_2":"160","sum":87382.8532451726},{"gid":284,"kilometers":1567.74726538161,"id_0":"1","id_1":"29","id_2":"284","sum":187453.088194076},{"gid":50,"kilometers":1145.33379580058,"id_0":"1","id_1":"5","id_2":"50","sum":165297.923540997},{"gid":33,"kilometers":192.465648395577,"id_0":"1","id_1":"4","id_2":"33","sum":175117.899841133},{"gid":57,"kilometers":940.374646584695,"id_0":"1","id_1":"7","id_2":"57","sum":49994.4457796076},{"gid":109,"kilometers":1283.67276544817,"id_0":"1","id_1":"11","id_2":"109","sum":59953.7035211446},{"gid":51,"kilometers":2400.91413929585,"id_0":"1","id_1":"5","id_2":"51","sum":124663.036812939},{"gid":239,"kilometers":448.3344568439,"id_0":"1","id_1":"24","id_2":"239","sum":39722.2685687994},{"gid":31,"kilometers":272.210934513081,"id_0":"1","id_1":"3","id_2":"31","sum":304583.054362024},{"gid":288,"kilometers":1165.95917401128,"id_0":"1","id_1":"30","id_2":"288","sum":87344.7612027712},{"gid":76,"kilometers":576.62046888737,"id_0":"1","id_1":"8","id_2":"76","sum":92864.9670954682},{"gid":69,"kilometers":1106.89591598631,"id_0":"1","id_1":"8","id_2":"69","sum":193231.142064795},{"gid":203,"kilometers":303.843283601822,"id_0":"1","id_1":"20","id_2":"203","sum":156094.531508446},{"gid":34,"kilometers":189.505988653825,"id_0":"1","id_1":"4","id_2":"34","sum":126070.401979879},{"gid":81,"kilometers":341.895975514414,"id_0":"1","id_1":"9","id_2":"81","sum":231212.354319513},{"gid":219,"kilometers":162.401792412705,"id_0":"1","id_1":"22","id_2":"219","sum":37928.6214282066},{"gid":12,"kilometers":4264.63577379165,"id_0":"1","id_1":"1","id_2":"12","sum":19074.3564081248},{"gid":273,"kilometers":290.852884028746,"id_0":"1","id_1":"27","id_2":"273","sum":75025.2470064098},{"gid":296,"kilometers":226.704676961548,"id_0":"1","id_1":"31","id_2":"296","sum":62064.9993756935},{"gid":282,"kilometers":395.639958792659,"id_0":"1","id_1":"28","id_2":"282","sum":59795.691191202},{"gid":10,"kilometers":348.748317460755,"id_0":"1","id_1":"1","id_2":"10","sum":75581.6042119851},{"gid":79,"kilometers":428.667050852766,"id_0":"1","id_1":"9","id_2":"79","sum":57027.4500111286},{"gid":321,"kilometers":442.678554615373,"id_0":"1","id_1":"34","id_2":"321","sum":20986.3225821536},{"gid":42,"kilometers":159.561567052624,"id_0":"1","id_1":"4","id_2":"42","sum":13449.4428776754},{"gid":324,"kilometers":707.173027359584,"id_0":"1","id_1":"34","id_2":"324","sum":54826.4252801612},{"gid":90,"kilometers":1068.00916790107,"id_0":"1","id_1":"9","id_2":"90","sum":69506.0085654929},{"gid":18,"kilometers":1817.93558526618,"id_0":"1","id_1":"2","id_2":"18","sum":172428.808921032},{"gid":134,"kilometers":741.190630122654,"id_0":"1","id_1":"13","id_2":"134","sum":37352.6382798366},{"gid":59,"kilometers":1013.20275109843,"id_0":"1","id_1":"7","id_2":"59","sum":120619.924798626},{"gid":139,"kilometers":125.354898720504,"id_0":"1","id_1":"14","id_2":"139","sum":928372.707306236},{"gid":146,"kilometers":251.128979799612,"id_0":"1","id_1":"14","id_2":"146","sum":38607.686198296},{"gid":143,"kilometers":44.5423406598951,"id_0":"1","id_1":"14","id_2":"143","sum":68526.0974982232},{"gid":192,"kilometers":254.070073022247,"id_0":"1","id_1":"18","id_2":"192","sum":43908.2465996891},{"gid":98,"kilometers":3269.86701381107,"id_0":"1","id_1":"10","id_2":"98","sum":142493.541944191},{"gid":193,"kilometers":81.7055112633344,"id_0":"1","id_1":"18","id_2":"193","sum":65329.5166257396},{"gid":271,"kilometers":837.038960701354,"id_0":"1","id_1":"27","id_2":"271","sum":68050.2692347104},{"gid":243,"kilometers":301.655706668996,"id_0":"1","id_1":"24","id_2":"243","sum":39282.4652396743},{"gid":167,"kilometers":60.0032134079316,"id_0":"1","id_1":"16","id_2":"167","sum":24151.8383107111},{"gid":250,"kilometers":180.610475435945,"id_0":"1","id_1":"25","id_2":"250","sum":18548.8090722375},{"gid":116,"kilometers":803.786762447582,"id_0":"1","id_1":"12","id_2":"116","sum":50610.708857975},{"gid":64,"kilometers":345.61248363829,"id_0":"1","id_1":"7","id_2":"64","sum":90325.0884292834},{"gid":235,"kilometers":508.115942182882,"id_0":"1","id_1":"23","id_2":"235","sum":31312.0131817251},{"gid":248,"kilometers":150.886091906805,"id_0":"1","id_1":"25","id_2":"248","sum":37888.5261362121},{"gid":84,"kilometers":123.00106902314,"id_0":"1","id_1":"9","id_2":"84","sum":216049.704737335},{"gid":145,"kilometers":15.6090259846347,"id_0":"1","id_1":"14","id_2":"145","sum":38773.1436927617},{"gid":207,"kilometers":391.329296076648,"id_0":"1","id_1":"20","id_2":"207","sum":173063.962113693},{"gid":198,"kilometers":355.685784104526,"id_0":"1","id_1":"19","id_2":"198","sum":172195.2215994},{"gid":74,"kilometers":208.898568695643,"id_0":"1","id_1":"8","id_2":"74","sum":52459.5073565803},{"gid":104,"kilometers":671.037739527959,"id_0":"1","id_1":"11","id_2":"104","sum":107625.356192753},{"gid":163,"kilometers":1416.89203085407,"id_0":"1","id_1":"15","id_2":"163","sum":76308.7789854482},{"gid":6,"kilometers":769.854131554713,"id_0":"1","id_1":"1","id_2":"6","sum":48180.2994881929},{"gid":102,"kilometers":3621.42344649258,"id_0":"1","id_1":"11","id_2":"102","sum":50304.2870501289},{"gid":315,"kilometers":461.279283512731,"id_0":"1","id_1":"33","id_2":"315","sum":79848.0891362168},{"gid":71,"kilometers":397.082433874123,"id_0":"1","id_1":"8","id_2":"71","sum":40075.2277829582},{"gid":246,"kilometers":85.535012258243,"id_0":"1","id_1":"25","id_2":"246","sum":45608.1286729556},{"gid":29,"kilometers":987.816928857021,"id_0":"1","id_1":"3","id_2":"29","sum":136984.723691926},{"gid":2,"kilometers":1127.65759388239,"id_0":"1","id_1":"1","id_2":"2","sum":87671.3143448667},{"gid":159,"kilometers":1376.77395451855,"id_0":"1","id_1":"15","id_2":"159","sum":52803.9871947877},{"gid":72,"kilometers":381.780138816983,"id_0":"1","id_1":"8","id_2":"72","sum":182736.424722359},{"gid":267,"kilometers":336.11913816342,"id_0":"1","id_1":"26","id_2":"267","sum":142829.855282661},{"gid":41,"kilometers":849.553668163426,"id_0":"1","id_1":"4","id_2":"41","sum":143363.174050643},{"gid":186,"kilometers":188.618534429709,"id_0":"1","id_1":"18","id_2":"186","sum":41718.4832450822},{"gid":234,"kilometers":8122.95826747167,"id_0":"1","id_1":"23","id_2":"234","sum":37586.6394137428},{"gid":177,"kilometers":78.9120070168167,"id_0":"1","id_1":"17","id_2":"177","sum":49652.6558021158},{"gid":141,"kilometers":258.432441898661,"id_0":"1","id_1":"14","id_2":"141","sum":1007214.22636572},{"gid":171,"kilometers":215.015315155807,"id_0":"1","id_1":"16","id_2":"171","sum":54286.4074384198},{"gid":227,"kilometers":40.0452461002614,"id_0":"1","id_1":"22","id_2":"227","sum":9526.48387188464},{"gid":247,"kilometers":1553.05023354795,"id_0":"1","id_1":"25","id_2":"247","sum":16318.5245107117},{"gid":280,"kilometers":363.089201344702,"id_0":"1","id_1":"28","id_2":"280","sum":40020.7837371328},{"gid":311,"kilometers":624.883266859193,"id_0":"1","id_1":"32","id_2":"311","sum":129964.152322546},{"gid":138,"kilometers":1042.87733556378,"id_0":"1","id_1":"13","id_2":"138","sum":262868.34983192},{"gid":242,"kilometers":702.31154284394,"id_0":"1","id_1":"24","id_2":"242","sum":52527.3256543432},{"gid":297,"kilometers":176.171747099564,"id_0":"1","id_1":"31","id_2":"297","sum":60354.4276213441},{"gid":190,"kilometers":125.462317523796,"id_0":"1","id_1":"18","id_2":"190","sum":49384.9910716861},{"gid":314,"kilometers":361.395270538346,"id_0":"1","id_1":"33","id_2":"314","sum":36011.175734573},{"gid":16,"kilometers":3059.64469826186,"id_0":"1","id_1":"2","id_2":"16","sum":148813.173022863},{"gid":54,"kilometers":952.509776523178,"id_0":"1","id_1":"6","id_2":"54","sum":80086.1545818858},{"gid":274,"kilometers":157.405122011711,"id_0":"1","id_1":"28","id_2":"274","sum":287841.065275043},{"gid":320,"kilometers":972.036491528335,"id_0":"1","id_1":"34","id_2":"320","sum":82779.2715166677},{"gid":47,"kilometers":723.010185062612,"id_0":"1","id_1":"5","id_2":"47","sum":116050.519411491},{"gid":103,"kilometers":5585.68120109389,"id_0":"1","id_1":"11","id_2":"103","sum":110556.38958386},{"gid":115,"kilometers":900.411248283759,"id_0":"1","id_1":"12","id_2":"115","sum":46567.6911495388},{"gid":148,"kilometers":49.7019705434333,"id_0":"1","id_1":"14","id_2":"148","sum":49741.8769567609},{"gid":236,"kilometers":2931.99165150785,"id_0":"1","id_1":"23","id_2":"236","sum":50037.9016859923},{"gid":208,"kilometers":135.34820097093,"id_0":"1","id_1":"21","id_2":"208","sum":142674.85303688},{"gid":323,"kilometers":386.961622697047,"id_0":"1","id_1":"34","id_2":"323","sum":20033.7573955581},{"gid":322,"kilometers":1147.56445610937,"id_0":"1","id_1":"34","id_2":"322","sum":148239.366234694},{"gid":211,"kilometers":416.240861907018,"id_0":"1","id_1":"21","id_2":"211","sum":124545.757548921},{"gid":46,"kilometers":533.332984542232,"id_0":"1","id_1":"4","id_2":"46","sum":82789.3510919549},{"gid":294,"kilometers":228.186520185013,"id_0":"1","id_1":"31","id_2":"294","sum":81778.5761659453},{"gid":83,"kilometers":393.841146364571,"id_0":"1","id_1":"9","id_2":"83","sum":88690.17455847},{"gid":36,"kilometers":520.357774633481,"id_0":"1","id_1":"4","id_2":"36","sum":102345.017596133},{"gid":15,"kilometers":681.630758102889,"id_0":"1","id_1":"2","id_2":"15","sum":69906.3764292374},{"gid":174,"kilometers":134.83130161018,"id_0":"1","id_1":"17","id_2":"174","sum":37592.6559143253},{"gid":304,"kilometers":1104.51412215948,"id_0":"1","id_1":"31","id_2":"304","sum":76381.9479929856},{"gid":77,"kilometers":942.38395970543,"id_0":"1","id_1":"8","id_2":"77","sum":219246.612400627},{"gid":125,"kilometers":1193.58975307915,"id_0":"1","id_1":"12","id_2":"125","sum":184975.095745139},{"gid":140,"kilometers":99.0062933656906,"id_0":"1","id_1":"14","id_2":"140","sum":71468.0667943209},{"gid":4,"kilometers":605.953912442938,"id_0":"1","id_1":"1","id_2":"4","sum":28611.7354970253},{"gid":153,"kilometers":211.089675949987,"id_0":"1","id_1":"15","id_2":"153","sum":135664.441271387},{"gid":200,"kilometers":293.765748366153,"id_0":"1","id_1":"19","id_2":"200","sum":206983.13910782},{"gid":257,"kilometers":674.174584038564,"id_0":"1","id_1":"25","id_2":"257","sum":117924.285195615},{"gid":73,"kilometers":877.524111799154,"id_0":"1","id_1":"8","id_2":"73","sum":54492.8492864771},{"gid":40,"kilometers":1379.83684832583,"id_0":"1","id_1":"4","id_2":"40","sum":109251.959795063},{"gid":56,"kilometers":4196.90650689132,"id_0":"1","id_1":"7","id_2":"56","sum":51831.5207912838},{"gid":182,"kilometers":175.24352737239,"id_0":"1","id_1":"17","id_2":"182","sum":78476.0354860257},{"gid":53,"kilometers":1396.7589687969,"id_0":"1","id_1":"6","id_2":"53","sum":119538.79937508},{"gid":222,"kilometers":94.8946559770791,"id_0":"1","id_1":"22","id_2":"222","sum":120249.939999573},{"gid":277,"kilometers":71.6815892129606,"id_0":"1","id_1":"28","id_2":"277","sum":141016.523967125},{"gid":123,"kilometers":1191.3610771003,"id_0":"1","id_1":"12","id_2":"123","sum":94399.4814139046},{"gid":261,"kilometers":79.7376395238657,"id_0":"1","id_1":"26","id_2":"261","sum":38157.1522095427},{"gid":275,"kilometers":96.6434263941868,"id_0":"1","id_1":"28","id_2":"275","sum":270117.018166661},{"gid":92,"kilometers":696.408227052174,"id_0":"1","id_1":"9","id_2":"92","sum":243824.579484779},{"gid":23,"kilometers":622.998934961342,"id_0":"1","id_1":"3","id_2":"23","sum":125163.944335522},{"gid":44,"kilometers":647.871582790161,"id_0":"1","id_1":"4","id_2":"44","sum":192165.03323833},{"gid":58,"kilometers":1962.67864210694,"id_0":"1","id_1":"7","id_2":"58","sum":99850.8760911208},{"gid":1,"kilometers":1159.81076320335,"id_0":"1","id_1":"1","id_2":"1","sum":118178.215293085},{"gid":189,"kilometers":203.79839460026,"id_0":"1","id_1":"18","id_2":"189","sum":81561.6518308446},{"gid":290,"kilometers":653.570840432377,"id_0":"1","id_1":"30","id_2":"290","sum":228285.24755396},{"gid":233,"kilometers":3942.5313584559,"id_0":"1","id_1":"23","id_2":"233","sum":47705.529145774},{"gid":111,"kilometers":3280.28491255369,"id_0":"1","id_1":"11","id_2":"111","sum":70215.3225017795},{"gid":206,"kilometers":234.791516940729,"id_0":"1","id_1":"20","id_2":"206","sum":171278.838323109},{"gid":237,"kilometers":312.654297396599,"id_0":"1","id_1":"23","id_2":"237","sum":73872.5477619502},{"gid":86,"kilometers":854.343459418245,"id_0":"1","id_1":"9","id_2":"86","sum":255988.342421427},{"gid":226,"kilometers":82.1970135009779,"id_0":"1","id_1":"22","id_2":"226","sum":82360.3245836496},{"gid":13,"kilometers":768.052168731602,"id_0":"1","id_1":"1","id_2":"13","sum":28753.4952347763},{"gid":49,"kilometers":435.854538134945,"id_0":"1","id_1":"5","id_2":"49","sum":42073.9444114747},{"gid":22,"kilometers":303.458743260007,"id_0":"1","id_1":"3","id_2":"22","sum":128629.756654586},{"gid":91,"kilometers":2126.68262416064,"id_0":"1","id_1":"9","id_2":"91","sum":179234.777383378},{"gid":70,"kilometers":1045.26281484687,"id_0":"1","id_1":"8","id_2":"70","sum":147632.854406858},{"gid":45,"kilometers":600.231284922273,"id_0":"1","id_1":"4","id_2":"45","sum":155474.840812519},{"gid":225,"kilometers":193.596450359636,"id_0":"1","id_1":"22","id_2":"225","sum":52347.8944997191},{"gid":199,"kilometers":619.410496648836,"id_0":"1","id_1":"19","id_2":"199","sum":329751.697925568},{"gid":303,"kilometers":319.646255552826,"id_0":"1","id_1":"31","id_2":"303","sum":304444.66821246},{"gid":293,"kilometers":348.057249813905,"id_0":"1","id_1":"30","id_2":"293","sum":73989.999910906},{"gid":60,"kilometers":2710.4455142914,"id_0":"1","id_1":"7","id_2":"60","sum":67520.0121609941},{"gid":165,"kilometers":2277.21571918777,"id_0":"1","id_1":"15","id_2":"165","sum":165281.275353983},{"gid":287,"kilometers":843.773343343445,"id_0":"1","id_1":"29","id_2":"287","sum":62760.3901256192},{"gid":105,"kilometers":680.941240700281,"id_0":"1","id_1":"11","id_2":"105","sum":191029.312947929},{"gid":327,"kilometers":644.899799134245,"id_0":"1","id_1":"34","id_2":"327","sum":27380.2854525596},{"gid":21,"kilometers":861.445273882213,"id_0":"1","id_1":"3","id_2":"21","sum":126158.952223981},{"gid":152,"kilometers":377.332499163918,"id_0":"1","id_1":"14","id_2":"152","sum":88110.4977539107},{"gid":75,"kilometers":466.824684839294,"id_0":"1","id_1":"8","id_2":"75","sum":173017.321661464},{"gid":124,"kilometers":715.924605128269,"id_0":"1","id_1":"12","id_2":"124","sum":109156.554746225},{"gid":5,"kilometers":1355.25759722949,"id_0":"1","id_1":"1","id_2":"5","sum":146850.985688911},{"gid":281,"kilometers":143.985700529837,"id_0":"1","id_1":"28","id_2":"281","sum":45673.3999417163},{"gid":229,"kilometers":282.090535431542,"id_0":"1","id_1":"22","id_2":"229","sum":337606.569082048},{"gid":301,"kilometers":290.048612847926,"id_0":"1","id_1":"31","id_2":"301","sum":147848.524895327},{"gid":245,"kilometers":574.901802008144,"id_0":"1","id_1":"25","id_2":"245","sum":42562.1322288457},{"gid":181,"kilometers":170.504830773556,"id_0":"1","id_1":"17","id_2":"181","sum":41576.567563463},{"gid":43,"kilometers":25.5425939302065,"id_0":"1","id_1":"4","id_2":"43","sum":494393.517746},{"gid":220,"kilometers":281.870557407121,"id_0":"1","id_1":"22","id_2":"220","sum":62010.581987571},{"gid":259,"kilometers":321.102657260243,"id_0":"1","id_1":"26","id_2":"259","sum":57342.2326461598},{"gid":166,"kilometers":103.460644302629,"id_0":"1","id_1":"16","id_2":"166","sum":63043.3987863213},{"gid":298,"kilometers":562.334482349123,"id_0":"1","id_1":"31","id_2":"298","sum":84247.3397631152},{"gid":291,"kilometers":1005.05771913613,"id_0":"1","id_1":"30","id_2":"291","sum":259309.543099651},{"gid":112,"kilometers":323.831206031137,"id_0":"1","id_1":"11","id_2":"112","sum":85351.8437814526},{"gid":254,"kilometers":211.911263869857,"id_0":"1","id_1":"25","id_2":"254","sum":98047.8422125056},{"gid":3,"kilometers":1135.03766752925,"id_0":"1","id_1":"1","id_2":"3","sum":327443.882372462},{"gid":201,"kilometers":251.305044792168,"id_0":"1","id_1":"19","id_2":"201","sum":450821.835188363},{"gid":61,"kilometers":458.570387912424,"id_0":"1","id_1":"7","id_2":"61","sum":34345.7758388277},{"gid":176,"kilometers":51.2516673402192,"id_0":"1","id_1":"17","id_2":"176","sum":89835.721245572},{"gid":96,"kilometers":1721.18225107132,"id_0":"1","id_1":"10","id_2":"96","sum":133125.439124518},{"gid":87,"kilometers":689.712032631641,"id_0":"1","id_1":"9","id_2":"87","sum":210115.630596943},{"gid":169,"kilometers":67.7867903303567,"id_0":"1","id_1":"16","id_2":"169","sum":128864.202910036},{"gid":14,"kilometers":526.600859631705,"id_0":"1","id_1":"2","id_2":"14","sum":50498.7626120728},{"gid":107,"kilometers":2042.52548674401,"id_0":"1","id_1":"11","id_2":"107","sum":173170.4059195},{"gid":319,"kilometers":446.470386101787,"id_0":"1","id_1":"33","id_2":"319","sum":185803.205560423},{"gid":147,"kilometers":27.3237646007784,"id_0":"1","id_1":"14","id_2":"147","sum":87595.6085325181},{"gid":221,"kilometers":64.2419382231489,"id_0":"1","id_1":"22","id_2":"221","sum":382177.215821445},{"gid":121,"kilometers":607.555101909951,"id_0":"1","id_1":"12","id_2":"121","sum":864645.669081919},{"gid":210,"kilometers":182.048949121532,"id_0":"1","id_1":"21","id_2":"210","sum":39480.0150515884},{"gid":258,"kilometers":98.2842856112495,"id_0":"1","id_1":"25","id_2":"258","sum":34862.3320286162},{"gid":161,"kilometers":2630.95927920094,"id_0":"1","id_1":"15","id_2":"161","sum":289924.79348518},{"gid":19,"kilometers":1236.05414574924,"id_0":"1","id_1":"2","id_2":"19","sum":141395.390346827},{"gid":108,"kilometers":646.358660747089,"id_0":"1","id_1":"11","id_2":"108","sum":179082.614998935},{"gid":157,"kilometers":242.680166184771,"id_0":"1","id_1":"15","id_2":"157","sum":702094.966690846},{"gid":154,"kilometers":1383.88857091081,"id_0":"1","id_1":"15","id_2":"154","sum":53061.3882251978},{"gid":215,"kilometers":73.5862700952875,"id_0":"1","id_1":"22","id_2":"215","sum":77001.0777076706},{"gid":35,"kilometers":524.075632429692,"id_0":"1","id_1":"4","id_2":"35","sum":81464.3280317513},{"gid":65,"kilometers":1608.86090585059,"id_0":"1","id_1":"7","id_2":"65","sum":57915.5412120521},{"gid":52,"kilometers":3122.05084188944,"id_0":"1","id_1":"6","id_2":"52","sum":390330.379224328},{"gid":317,"kilometers":87.2273435313134,"id_0":"1","id_1":"33","id_2":"317","sum":54608.4840187579},{"gid":37,"kilometers":585.18838392882,"id_0":"1","id_1":"4","id_2":"37","sum":98781.9984341282},{"gid":85,"kilometers":335.38312015953,"id_0":"1","id_1":"9","id_2":"85","sum":57803.3045039512},{"gid":173,"kilometers":107.185841728965,"id_0":"1","id_1":"17","id_2":"173","sum":38511.0460024402},{"gid":32,"kilometers":1116.19007548872,"id_0":"1","id_1":"3","id_2":"32","sum":61282.2067008894},{"gid":188,"kilometers":131.703990253221,"id_0":"1","id_1":"18","id_2":"188","sum":36979.6314862892},{"gid":164,"kilometers":1668.94229789556,"id_0":"1","id_1":"15","id_2":"164","sum":17446.9044663366},{"gid":326,"kilometers":1147.37103126497,"id_0":"1","id_1":"34","id_2":"326","sum":42791.1445260923},{"gid":78,"kilometers":860.139835809544,"id_0":"1","id_1":"8","id_2":"78","sum":161524.604438134},{"gid":306,"kilometers":1274.28985626189,"id_0":"1","id_1":"32","id_2":"306","sum":122396.430250252},{"gid":278,"kilometers":265.168174804868,"id_0":"1","id_1":"28","id_2":"278","sum":101515.102146722},{"gid":133,"kilometers":303.201763646335,"id_0":"1","id_1":"13","id_2":"133","sum":19849.0115854423},{"gid":289,"kilometers":2510.34266323918,"id_0":"1","id_1":"30","id_2":"289","sum":147237.455234516},{"gid":279,"kilometers":254.52092489891,"id_0":"1","id_1":"28","id_2":"279","sum":53761.4468262941},{"gid":150,"kilometers":50.161631249722,"id_0":"1","id_1":"14","id_2":"150","sum":107371.741187215},{"gid":308,"kilometers":956.951707373191,"id_0":"1","id_1":"32","id_2":"308","sum":85075.0386303458},{"gid":100,"kilometers":1165.75899498267,"id_0":"1","id_1":"10","id_2":"100","sum":78978.2798696226},{"gid":232,"kilometers":146.527842956699,"id_0":"1","id_1":"22","id_2":"232","sum":208492.170855217},{"gid":158,"kilometers":483.294180142336,"id_0":"1","id_1":"15","id_2":"158","sum":31247.5416341238},{"gid":113,"kilometers":1903.1416790817,"id_0":"1","id_1":"11","id_2":"113","sum":30871.9945398485},{"gid":172,"kilometers":86.3452167047955,"id_0":"1","id_1":"17","id_2":"172","sum":48675.6614808124},{"gid":63,"kilometers":2473.88193417178,"id_0":"1","id_1":"7","id_2":"63","sum":98093.1366920415},{"gid":9,"kilometers":711.305441571042,"id_0":"1","id_1":"1","id_2":"9","sum":218694.405525535},{"gid":183,"kilometers":194.801883950795,"id_0":"1","id_1":"17","id_2":"183","sum":95967.7535358667},{"gid":24,"kilometers":310.583668120355,"id_0":"1","id_1":"3","id_2":"24","sum":87848.633720234},{"gid":184,"kilometers":262.425643605105,"id_0":"1","id_1":"18","id_2":"184","sum":114025.241493894},{"gid":260,"kilometers":109.488275931729,"id_0":"1","id_1":"26","id_2":"260","sum":76600.9344955608},{"gid":170,"kilometers":221.526049839187,"id_0":"1","id_1":"16","id_2":"170","sum":198457.835977055},{"gid":55,"kilometers":1442.83837421058,"id_0":"1","id_1":"6","id_2":"55","sum":152842.630082349},{"gid":68,"kilometers":277.359631209563,"id_0":"1","id_1":"8","id_2":"68","sum":64337.1762048751},{"gid":88,"kilometers":492.553282781882,"id_0":"1","id_1":"9","id_2":"88","sum":110085.824159759},{"gid":118,"kilometers":2224.62499147684,"id_0":"1","id_1":"12","id_2":"118","sum":131373.300383665},{"gid":244,"kilometers":511.346123944078,"id_0":"1","id_1":"25","id_2":"244","sum":48596.8306273278},{"gid":130,"kilometers":378.92546454281,"id_0":"1","id_1":"13","id_2":"130","sum":138782.546137301},{"gid":272,"kilometers":275.742522923779,"id_0":"1","id_1":"27","id_2":"272","sum":36640.4077247093},{"gid":38,"kilometers":120.725819077089,"id_0":"1","id_1":"4","id_2":"38","sum":113550.057674387},{"gid":128,"kilometers":2852.2392097553,"id_0":"1","id_1":"12","id_2":"128","sum":251484.659274844},{"gid":223,"kilometers":222.850660353271,"id_0":"1","id_1":"22","id_2":"223","sum":180591.546289958},{"gid":240,"kilometers":945.916379036922,"id_0":"1","id_1":"24","id_2":"240","sum":40662.1477455795},{"gid":283,"kilometers":575.962378147298,"id_0":"1","id_1":"29","id_2":"283","sum":149854.869793926},{"gid":300,"kilometers":282.038394209894,"id_0":"1","id_1":"31","id_2":"300","sum":114783.792401877},{"gid":217,"kilometers":207.591971412919,"id_0":"1","id_1":"22","id_2":"217","sum":102438.066528477},{"gid":231,"kilometers":64.2328360027158,"id_0":"1","id_1":"22","id_2":"231","sum":104429.764246121},{"gid":212,"kilometers":446.96675487333,"id_0":"1","id_1":"21","id_2":"212","sum":162791.153463438},{"gid":328,"kilometers":662.595222920105,"id_0":"1","id_1":"34","id_2":"328","sum":30509.7609187514},{"gid":256,"kilometers":1223.27075124554,"id_0":"1","id_1":"25","id_2":"256","sum":5520.28500962746},{"gid":216,"kilometers":105.266188569528,"id_0":"1","id_1":"22","id_2":"216","sum":77027.2396590561},{"gid":263,"kilometers":169.171082840676,"id_0":"1","id_1":"26","id_2":"263","sum":54900.4945745878},{"gid":202,"kilometers":810.552327306044,"id_0":"1","id_1":"19","id_2":"202","sum":106789.838084007},{"gid":266,"kilometers":176.848195228743,"id_0":"1","id_1":"26","id_2":"266","sum":88057.641340334},{"gid":144,"kilometers":81.3524686526222,"id_0":"1","id_1":"14","id_2":"144","sum":4360588.95269147},{"gid":7,"kilometers":1146.90700682532,"id_0":"1","id_1":"1","id_2":"7","sum":292904.625589502}] 2 | -------------------------------------------------------------------------------- /public/aggregations/mosquito/aegypti/simon_hay/gadm2-8/afg_2_gadm2-8^aegypti^simon_hay^0.12469^376427.json: -------------------------------------------------------------------------------- 1 | [{"gid":170,"kilometers":221.526049839187,"id_0":"1","id_1":"16","id_2":"170","mean":0.134451949145065},{"gid":250,"kilometers":180.610475435945,"id_0":"1","id_1":"25","id_2":"250","mean":0.0167634032894597},{"gid":103,"kilometers":5585.68120109389,"id_0":"1","id_1":"11","id_2":"103","mean":0.0541040438561182},{"gid":303,"kilometers":319.646255552826,"id_0":"1","id_1":"31","id_2":"303","mean":0.350020584550602},{"gid":10,"kilometers":348.748317460755,"id_0":"1","id_1":"1","id_2":"10","mean":0.0599581492004324},{"gid":46,"kilometers":533.332984542232,"id_0":"1","id_1":"4","id_2":"46","mean":0.13551584946299},{"gid":265,"kilometers":136.250402795582,"id_0":"1","id_1":"26","id_2":"265","mean":0.0448728315474302},{"gid":313,"kilometers":363.619932599003,"id_0":"1","id_1":"33","id_2":"313","mean":0.0120389842672018},{"gid":123,"kilometers":1191.3610771003,"id_0":"1","id_1":"12","id_2":"123","mean":0.147484093931571},{"gid":280,"kilometers":363.089201344702,"id_0":"1","id_1":"28","id_2":"280","mean":0.0156671732821566},{"gid":32,"kilometers":1116.19007548872,"id_0":"1","id_1":"3","id_2":"32","mean":0.0309211798953142},{"gid":292,"kilometers":494.943683933709,"id_0":"1","id_1":"30","id_2":"292","mean":0.041501908273471},{"gid":91,"kilometers":2126.68262416064,"id_0":"1","id_1":"9","id_2":"91","mean":0.0094695520882246},{"gid":204,"kilometers":216.298231103239,"id_0":"1","id_1":"20","id_2":"204","mean":0.0419248458765352},{"gid":264,"kilometers":190.264587268741,"id_0":"1","id_1":"26","id_2":"264","mean":0.0212546979476079},{"gid":186,"kilometers":188.618534429709,"id_0":"1","id_1":"18","id_2":"186","mean":0.0245592899849024},{"gid":190,"kilometers":125.462317523796,"id_0":"1","id_1":"18","id_2":"190","mean":0.543421837423185},{"gid":311,"kilometers":624.883266859193,"id_0":"1","id_1":"32","id_2":"311","mean":0.245346671616958},{"gid":94,"kilometers":4276.6738870403,"id_0":"1","id_1":"10","id_2":"94","mean":0.0174308353269451},{"gid":154,"kilometers":1383.88857091081,"id_0":"1","id_1":"15","id_2":"154","mean":0.151129495580789},{"gid":111,"kilometers":3280.28491255369,"id_0":"1","id_1":"11","id_2":"111","mean":0.0459705318383153},{"gid":83,"kilometers":393.841146364571,"id_0":"1","id_1":"9","id_2":"83","mean":0.0480200434919619},{"gid":311,"kilometers":624.883266859193,"id_0":"1","id_1":"32","id_2":"311","mean":0.181025505361752},{"gid":298,"kilometers":562.334482349123,"id_0":"1","id_1":"31","id_2":"298","mean":0.0404434162764261},{"gid":75,"kilometers":466.824684839294,"id_0":"1","id_1":"8","id_2":"75","mean":0.0641934924272074},{"gid":97,"kilometers":1292.25377934809,"id_0":"1","id_1":"10","id_2":"97","mean":0.0256887329572396},{"gid":274,"kilometers":157.405122011711,"id_0":"1","id_1":"28","id_2":"274","mean":null},{"gid":292,"kilometers":494.943683933709,"id_0":"1","id_1":"30","id_2":"292","mean":0.101332614771579},{"gid":80,"kilometers":626.896026300101,"id_0":"1","id_1":"9","id_2":"80","mean":0.0152201520366262},{"gid":321,"kilometers":442.678554615373,"id_0":"1","id_1":"34","id_2":"321","mean":0.0843286752523015},{"gid":86,"kilometers":854.343459418245,"id_0":"1","id_1":"9","id_2":"86","mean":0.0169514868384138},{"gid":188,"kilometers":131.703990253221,"id_0":"1","id_1":"18","id_2":"188","mean":0.312739073526122},{"gid":92,"kilometers":696.408227052174,"id_0":"1","id_1":"9","id_2":"92","mean":0.044566552396719},{"gid":94,"kilometers":4276.6738870403,"id_0":"1","id_1":"10","id_2":"94","mean":0.0240581228997484},{"gid":7,"kilometers":1146.90700682532,"id_0":"1","id_1":"1","id_2":"7","mean":0.0664219133745208},{"gid":152,"kilometers":377.332499163918,"id_0":"1","id_1":"14","id_2":"152","mean":0.173607591526396},{"gid":323,"kilometers":386.961622697047,"id_0":"1","id_1":"34","id_2":"323","mean":0.178477577444481},{"gid":140,"kilometers":99.0062933656906,"id_0":"1","id_1":"14","id_2":"140","mean":0.0770699020547667},{"gid":73,"kilometers":877.524111799154,"id_0":"1","id_1":"8","id_2":"73","mean":0.0303151341293618},{"gid":16,"kilometers":3059.64469826186,"id_0":"1","id_1":"2","id_2":"16","mean":0.0283884697710279},{"gid":226,"kilometers":82.1970135009779,"id_0":"1","id_1":"22","id_2":"226","mean":0.386900685853173},{"gid":291,"kilometers":1005.05771913613,"id_0":"1","id_1":"30","id_2":"291","mean":0.11844134616602},{"gid":143,"kilometers":44.5423406598951,"id_0":"1","id_1":"14","id_2":"143","mean":0.0253202201840831},{"gid":148,"kilometers":49.7019705434333,"id_0":"1","id_1":"14","id_2":"148","mean":0.113256173901479},{"gid":21,"kilometers":861.445273882213,"id_0":"1","id_1":"3","id_2":"21","mean":0.0272612368483695},{"gid":279,"kilometers":254.52092489891,"id_0":"1","id_1":"28","id_2":"279","mean":0.0510588091658219},{"gid":90,"kilometers":1068.00916790107,"id_0":"1","id_1":"9","id_2":"90","mean":0.0576189867443516},{"gid":110,"kilometers":274.859596134037,"id_0":"1","id_1":"11","id_2":"110","mean":0.211660213281795},{"gid":119,"kilometers":673.290176683657,"id_0":"1","id_1":"12","id_2":"119","mean":0.136540336746699},{"gid":246,"kilometers":85.535012258243,"id_0":"1","id_1":"25","id_2":"246","mean":0.0327136218450404},{"gid":238,"kilometers":722.19671311811,"id_0":"1","id_1":"24","id_2":"238","mean":0.0150296456842902},{"gid":138,"kilometers":1042.87733556378,"id_0":"1","id_1":"13","id_2":"138","mean":0.156537424253591},{"gid":78,"kilometers":860.139835809544,"id_0":"1","id_1":"8","id_2":"78","mean":0.231875165736804},{"gid":165,"kilometers":2277.21571918777,"id_0":"1","id_1":"15","id_2":"165","mean":0.0695884843072399},{"gid":241,"kilometers":441.463743962843,"id_0":"1","id_1":"24","id_2":"241","mean":0.312814869985432},{"gid":280,"kilometers":363.089201344702,"id_0":"1","id_1":"28","id_2":"280","mean":0.0200670138073356},{"gid":312,"kilometers":488.873688079503,"id_0":"1","id_1":"33","id_2":"312","mean":0.01499912131471},{"gid":324,"kilometers":707.173027359584,"id_0":"1","id_1":"34","id_2":"324","mean":0.151954372379955},{"gid":194,"kilometers":142.136341307294,"id_0":"1","id_1":"18","id_2":"194","mean":0.327354228915363},{"gid":182,"kilometers":175.24352737239,"id_0":"1","id_1":"17","id_2":"182","mean":0.142158873100044},{"gid":129,"kilometers":803.630066198859,"id_0":"1","id_1":"12","id_2":"129","mean":0.10440408378119},{"gid":155,"kilometers":1703.80988971031,"id_0":"1","id_1":"15","id_2":"155","mean":0.0680737191920126},{"gid":71,"kilometers":397.082433874123,"id_0":"1","id_1":"8","id_2":"71","mean":0.0803389366255459},{"gid":214,"kilometers":59.609538699944,"id_0":"1","id_1":"22","id_2":"214","mean":0.507344154350067},{"gid":219,"kilometers":162.401792412705,"id_0":"1","id_1":"22","id_2":"219","mean":0.400296442891024},{"gid":45,"kilometers":600.231284922273,"id_0":"1","id_1":"4","id_2":"45","mean":0.111110274252247},{"gid":34,"kilometers":189.505988653825,"id_0":"1","id_1":"4","id_2":"34","mean":0.24845590300028},{"gid":310,"kilometers":778.811594485114,"id_0":"1","id_1":"32","id_2":"310","mean":0.0931132498123027},{"gid":319,"kilometers":446.470386101787,"id_0":"1","id_1":"33","id_2":"319","mean":0.017830225044748},{"gid":38,"kilometers":120.725819077089,"id_0":"1","id_1":"4","id_2":"38","mean":0.422457909597037},{"gid":41,"kilometers":849.553668163426,"id_0":"1","id_1":"4","id_2":"41","mean":0.0597613822047368},{"gid":293,"kilometers":348.057249813905,"id_0":"1","id_1":"30","id_2":"293","mean":0.120417846071461},{"gid":220,"kilometers":281.870557407121,"id_0":"1","id_1":"22","id_2":"220","mean":0.075863430320312},{"gid":179,"kilometers":63.9356896573351,"id_0":"1","id_1":"17","id_2":"179","mean":0.0940482691439201},{"gid":188,"kilometers":131.703990253221,"id_0":"1","id_1":"18","id_2":"188","mean":0.220185733691931},{"gid":134,"kilometers":741.190630122654,"id_0":"1","id_1":"13","id_2":"134","mean":0.0739007528426552},{"gid":263,"kilometers":169.171082840676,"id_0":"1","id_1":"26","id_2":"263","mean":0.0151057713965396},{"gid":87,"kilometers":689.712032631641,"id_0":"1","id_1":"9","id_2":"87","mean":0.0129553773851448},{"gid":180,"kilometers":141.272839518683,"id_0":"1","id_1":"17","id_2":"180","mean":0.595559080599374},{"gid":187,"kilometers":101.280472077285,"id_0":"1","id_1":"18","id_2":"187","mean":0.345368783660694},{"gid":72,"kilometers":381.780138816983,"id_0":"1","id_1":"8","id_2":"72","mean":0.27723890031053},{"gid":107,"kilometers":2042.52548674401,"id_0":"1","id_1":"11","id_2":"107","mean":0.0737812412371625},{"gid":31,"kilometers":272.210934513081,"id_0":"1","id_1":"3","id_2":"31","mean":0.43143581611269},{"gid":50,"kilometers":1145.33379580058,"id_0":"1","id_1":"5","id_2":"50","mean":0.00967160031872503},{"gid":233,"kilometers":3942.5313584559,"id_0":"1","id_1":"23","id_2":"233","mean":0.0416321624897358},{"gid":261,"kilometers":79.7376395238657,"id_0":"1","id_1":"26","id_2":"261","mean":null},{"gid":241,"kilometers":441.463743962843,"id_0":"1","id_1":"24","id_2":"241","mean":0.033229211159897},{"gid":173,"kilometers":107.185841728965,"id_0":"1","id_1":"17","id_2":"173","mean":0.365248236865958},{"gid":257,"kilometers":674.174584038564,"id_0":"1","id_1":"25","id_2":"257","mean":0.0432555886865302},{"gid":315,"kilometers":461.279283512731,"id_0":"1","id_1":"33","id_2":"315","mean":0.0140951963680657},{"gid":11,"kilometers":1732.03561227776,"id_0":"1","id_1":"1","id_2":"11","mean":0.0202699287578145},{"gid":296,"kilometers":226.704676961548,"id_0":"1","id_1":"31","id_2":"296","mean":0.0574326200157501},{"gid":272,"kilometers":275.742522923779,"id_0":"1","id_1":"27","id_2":"272","mean":0.0237042398488911},{"gid":275,"kilometers":96.6434263941868,"id_0":"1","id_1":"28","id_2":"275","mean":0.216802700087768},{"gid":277,"kilometers":71.6815892129606,"id_0":"1","id_1":"28","id_2":"277","mean":0.226040638916837},{"gid":125,"kilometers":1193.58975307915,"id_0":"1","id_1":"12","id_2":"125","mean":0.227413512329094},{"gid":300,"kilometers":282.038394209894,"id_0":"1","id_1":"31","id_2":"300","mean":0.10687901204894},{"gid":12,"kilometers":4264.63577379165,"id_0":"1","id_1":"1","id_2":"12","mean":0.00525672613451335},{"gid":171,"kilometers":215.015315155807,"id_0":"1","id_1":"16","id_2":"171","mean":0.179987994100414},{"gid":224,"kilometers":138.871955015933,"id_0":"1","id_1":"22","id_2":"224","mean":0.523688147360611},{"gid":278,"kilometers":265.168174804868,"id_0":"1","id_1":"28","id_2":"278","mean":0.126589610128783},{"gid":309,"kilometers":584.172542793481,"id_0":"1","id_1":"32","id_2":"309","mean":0.110771066280969},{"gid":106,"kilometers":780.301645621907,"id_0":"1","id_1":"11","id_2":"106","mean":0.105390244348313},{"gid":167,"kilometers":60.0032134079316,"id_0":"1","id_1":"16","id_2":"167","mean":0.0959973345501425},{"gid":124,"kilometers":715.924605128269,"id_0":"1","id_1":"12","id_2":"124","mean":0.198923019053084},{"gid":286,"kilometers":937.118842010791,"id_0":"1","id_1":"29","id_2":"286","mean":0.0262289912358559},{"gid":114,"kilometers":4282.84843368105,"id_0":"1","id_1":"12","id_2":"114","mean":0.0456840899360395},{"gid":270,"kilometers":597.362088285564,"id_0":"1","id_1":"26","id_2":"270","mean":0.0425727171174868},{"gid":26,"kilometers":671.91726168243,"id_0":"1","id_1":"3","id_2":"26","mean":0.133891998407312},{"gid":151,"kilometers":133.771650511014,"id_0":"1","id_1":"14","id_2":"151","mean":0.0939130727247638},{"gid":169,"kilometers":67.7867903303567,"id_0":"1","id_1":"16","id_2":"169","mean":0.173022075294398},{"gid":298,"kilometers":562.334482349123,"id_0":"1","id_1":"31","id_2":"298","mean":0.0308284409563301},{"gid":159,"kilometers":1376.77395451855,"id_0":"1","id_1":"15","id_2":"159","mean":0.0935556115594553},{"gid":327,"kilometers":644.899799134245,"id_0":"1","id_1":"34","id_2":"327","mean":0.0785161046251853},{"gid":137,"kilometers":493.089365715389,"id_0":"1","id_1":"13","id_2":"137","mean":null},{"gid":139,"kilometers":125.354898720504,"id_0":"1","id_1":"14","id_2":"139","mean":0.0849984832488226},{"gid":210,"kilometers":182.048949121532,"id_0":"1","id_1":"21","id_2":"210","mean":0.0217279208857793},{"gid":283,"kilometers":575.962378147298,"id_0":"1","id_1":"29","id_2":"283","mean":0.104478832160502},{"gid":297,"kilometers":176.171747099564,"id_0":"1","id_1":"31","id_2":"297","mean":0.482320823268485},{"gid":142,"kilometers":47.3162834762632,"id_0":"1","id_1":"14","id_2":"142","mean":0.131838465660328},{"gid":295,"kilometers":256.991160818164,"id_0":"1","id_1":"31","id_2":"295","mean":0.232411542986467},{"gid":1,"kilometers":1159.81076320335,"id_0":"1","id_1":"1","id_2":"1","mean":0.0208698950410302},{"gid":29,"kilometers":987.816928857021,"id_0":"1","id_1":"3","id_2":"29","mean":0.0265280099106644},{"gid":131,"kilometers":516.497211319805,"id_0":"1","id_1":"13","id_2":"131","mean":0.107329973254748},{"gid":94,"kilometers":4276.6738870403,"id_0":"1","id_1":"10","id_2":"94","mean":0.0146943156630969},{"gid":185,"kilometers":251.458234253374,"id_0":"1","id_1":"18","id_2":"185","mean":null},{"gid":149,"kilometers":162.772625224264,"id_0":"1","id_1":"14","id_2":"149","mean":0.0593233865245082},{"gid":51,"kilometers":2400.91413929585,"id_0":"1","id_1":"5","id_2":"51","mean":0.0102561112065853},{"gid":173,"kilometers":107.185841728965,"id_0":"1","id_1":"17","id_2":"173","mean":0.257867306220402},{"gid":105,"kilometers":680.941240700281,"id_0":"1","id_1":"11","id_2":"105","mean":0.112536220853337},{"gid":205,"kilometers":337.195385976917,"id_0":"1","id_1":"20","id_2":"205","mean":0.0373458255136459},{"gid":274,"kilometers":157.405122011711,"id_0":"1","id_1":"28","id_2":"274","mean":0.23187496172055},{"gid":62,"kilometers":2489.19048642287,"id_0":"1","id_1":"7","id_2":"62","mean":0.0625988994719747},{"gid":161,"kilometers":2630.95927920094,"id_0":"1","id_1":"15","id_2":"161","mean":0.0602643811734761},{"gid":170,"kilometers":221.526049839187,"id_0":"1","id_1":"16","id_2":"170","mean":0.0628956609969974},{"gid":237,"kilometers":312.654297396599,"id_0":"1","id_1":"23","id_2":"237","mean":null},{"gid":15,"kilometers":681.630758102889,"id_0":"1","id_1":"2","id_2":"15","mean":0.19606419199717},{"gid":58,"kilometers":1962.67864210694,"id_0":"1","id_1":"7","id_2":"58","mean":0.0727776365127818},{"gid":136,"kilometers":381.533773748992,"id_0":"1","id_1":"13","id_2":"136","mean":0.128908835315434},{"gid":318,"kilometers":271.264572800321,"id_0":"1","id_1":"33","id_2":"318","mean":0.0191144278243402},{"gid":109,"kilometers":1283.67276544817,"id_0":"1","id_1":"11","id_2":"109","mean":0.084324328213731},{"gid":127,"kilometers":680.21472678846,"id_0":"1","id_1":"12","id_2":"127","mean":0.157909920699151},{"gid":174,"kilometers":134.83130161018,"id_0":"1","id_1":"17","id_2":"174","mean":0.327721367841416},{"gid":276,"kilometers":525.701334633467,"id_0":"1","id_1":"28","id_2":"276","mean":0.0231786795602609},{"gid":74,"kilometers":208.898568695643,"id_0":"1","id_1":"8","id_2":"74","mean":0.173146913021846},{"gid":52,"kilometers":3122.05084188944,"id_0":"1","id_1":"6","id_2":"52","mean":0.0192284065507479},{"gid":198,"kilometers":355.685784104526,"id_0":"1","id_1":"19","id_2":"198","mean":0.275675952138149},{"gid":212,"kilometers":446.96675487333,"id_0":"1","id_1":"21","id_2":"212","mean":0.056406074277806},{"gid":94,"kilometers":4276.6738870403,"id_0":"1","id_1":"10","id_2":"94","mean":0.0132867790144975},{"gid":84,"kilometers":123.00106902314,"id_0":"1","id_1":"9","id_2":"84","mean":0.164359870086559},{"gid":25,"kilometers":522.765048089007,"id_0":"1","id_1":"3","id_2":"25","mean":0.0672804500673572},{"gid":138,"kilometers":1042.87733556378,"id_0":"1","id_1":"13","id_2":"138","mean":0.154703523244027},{"gid":271,"kilometers":837.038960701354,"id_0":"1","id_1":"27","id_2":"271","mean":0.0250635293754909},{"gid":144,"kilometers":81.3524686526222,"id_0":"1","id_1":"14","id_2":"144","mean":0.253926210271724},{"gid":185,"kilometers":251.458234253374,"id_0":"1","id_1":"18","id_2":"185","mean":0.121281381623072},{"gid":24,"kilometers":310.583668120355,"id_0":"1","id_1":"3","id_2":"24","mean":0.134787558827062},{"gid":192,"kilometers":254.070073022247,"id_0":"1","id_1":"18","id_2":"192","mean":0.104473109660436},{"gid":104,"kilometers":671.037739527959,"id_0":"1","id_1":"11","id_2":"104","mean":0.101086032526051},{"gid":213,"kilometers":184.825276934487,"id_0":"1","id_1":"22","id_2":"213","mean":0.433035408249628},{"gid":300,"kilometers":282.038394209894,"id_0":"1","id_1":"31","id_2":"300","mean":0.218372532430058},{"gid":12,"kilometers":4264.63577379165,"id_0":"1","id_1":"1","id_2":"12","mean":0.00622762601593402},{"gid":195,"kilometers":88.9560273953211,"id_0":"1","id_1":"18","id_2":"195","mean":0.592514953799128},{"gid":48,"kilometers":767.354086995045,"id_0":"1","id_1":"5","id_2":"48","mean":0.0095671851753442},{"gid":316,"kilometers":965.170732329231,"id_0":"1","id_1":"33","id_2":"316","mean":0.0105879163165325},{"gid":103,"kilometers":5585.68120109389,"id_0":"1","id_1":"11","id_2":"103","mean":0.0821634181047418},{"gid":114,"kilometers":4282.84843368105,"id_0":"1","id_1":"12","id_2":"114","mean":0.0631198893056097},{"gid":229,"kilometers":282.090535431542,"id_0":"1","id_1":"22","id_2":"229","mean":0.354355488136214},{"gid":253,"kilometers":382.924859130722,"id_0":"1","id_1":"25","id_2":"253","mean":0.0491948381521971},{"gid":96,"kilometers":1721.18225107132,"id_0":"1","id_1":"10","id_2":"96","mean":0.0197506696486703},{"gid":65,"kilometers":1608.86090585059,"id_0":"1","id_1":"7","id_2":"65","mean":0.0866780102871337},{"gid":288,"kilometers":1165.95917401128,"id_0":"1","id_1":"30","id_2":"288","mean":0.0243290895389675},{"gid":205,"kilometers":337.195385976917,"id_0":"1","id_1":"20","id_2":"205","mean":0.0243832787567442},{"gid":289,"kilometers":2510.34266323918,"id_0":"1","id_1":"30","id_2":"289","mean":0.0224416769683589},{"gid":164,"kilometers":1668.94229789556,"id_0":"1","id_1":"15","id_2":"164","mean":0.0682923151626015},{"gid":306,"kilometers":1274.28985626189,"id_0":"1","id_1":"32","id_2":"306","mean":0.0704539825008039},{"gid":99,"kilometers":1090.1863394003,"id_0":"1","id_1":"10","id_2":"99","mean":0.0271232983693393},{"gid":223,"kilometers":222.850660353271,"id_0":"1","id_1":"22","id_2":"223","mean":0.262419055526062},{"gid":98,"kilometers":3269.86701381107,"id_0":"1","id_1":"10","id_2":"98","mean":0.020052176668489},{"gid":216,"kilometers":105.266188569528,"id_0":"1","id_1":"22","id_2":"216","mean":0.224150262663886},{"gid":290,"kilometers":653.570840432377,"id_0":"1","id_1":"30","id_2":"290","mean":0.0660789816087152},{"gid":181,"kilometers":170.504830773556,"id_0":"1","id_1":"17","id_2":"181","mean":0.0376336075342336},{"gid":189,"kilometers":203.79839460026,"id_0":"1","id_1":"18","id_2":"189","mean":0.268010415259471},{"gid":282,"kilometers":395.639958792659,"id_0":"1","id_1":"28","id_2":"282","mean":0.0187585821741288},{"gid":261,"kilometers":79.7376395238657,"id_0":"1","id_1":"26","id_2":"261","mean":0.0787563216638114},{"gid":76,"kilometers":576.62046888737,"id_0":"1","id_1":"8","id_2":"76","mean":0.186524119513309},{"gid":93,"kilometers":101.552987508959,"id_0":"1","id_1":"9","id_2":"93","mean":0.0112632361122614},{"gid":162,"kilometers":5612.96407206531,"id_0":"1","id_1":"15","id_2":"162","mean":0.0622507203046536},{"gid":285,"kilometers":424.275603063926,"id_0":"1","id_1":"29","id_2":"285","mean":0.18626548165915},{"gid":47,"kilometers":723.010185062612,"id_0":"1","id_1":"5","id_2":"47","mean":0.0122442603511911},{"gid":161,"kilometers":2630.95927920094,"id_0":"1","id_1":"15","id_2":"161","mean":0.0971896307528418},{"gid":217,"kilometers":207.591971412919,"id_0":"1","id_1":"22","id_2":"217","mean":0.138247003768913},{"gid":236,"kilometers":2931.99165150785,"id_0":"1","id_1":"23","id_2":"236","mean":0.0554328602729998},{"gid":287,"kilometers":843.773343343445,"id_0":"1","id_1":"29","id_2":"287","mean":0.0150876950865636},{"gid":266,"kilometers":176.848195228743,"id_0":"1","id_1":"26","id_2":"266","mean":0.024279971646944},{"gid":116,"kilometers":803.786762447582,"id_0":"1","id_1":"12","id_2":"116","mean":0.0219633804816625},{"gid":112,"kilometers":323.831206031137,"id_0":"1","id_1":"11","id_2":"112","mean":0.110013778367848},{"gid":122,"kilometers":748.615587019429,"id_0":"1","id_1":"12","id_2":"122","mean":0.0916654460250859},{"gid":289,"kilometers":2510.34266323918,"id_0":"1","id_1":"30","id_2":"289","mean":0.00943411548429594},{"gid":16,"kilometers":3059.64469826186,"id_0":"1","id_1":"2","id_2":"16","mean":0.0205166262553258},{"gid":239,"kilometers":448.3344568439,"id_0":"1","id_1":"24","id_2":"239","mean":0.0297401332008468},{"gid":206,"kilometers":234.791516940729,"id_0":"1","id_1":"20","id_2":"206","mean":0.500174499720553},{"gid":49,"kilometers":435.854538134945,"id_0":"1","id_1":"5","id_2":"49","mean":0.0137674289636167},{"gid":222,"kilometers":94.8946559770791,"id_0":"1","id_1":"22","id_2":"222","mean":0.392087316578889},{"gid":258,"kilometers":98.2842856112495,"id_0":"1","id_1":"25","id_2":"258","mean":0.0119539363491327},{"gid":56,"kilometers":4196.90650689132,"id_0":"1","id_1":"7","id_2":"56","mean":0.0766727779992133},{"gid":141,"kilometers":258.432441898661,"id_0":"1","id_1":"14","id_2":"141","mean":0.0738497966145162},{"gid":163,"kilometers":1416.89203085407,"id_0":"1","id_1":"15","id_2":"163","mean":0.158238124910221},{"gid":100,"kilometers":1165.75899498267,"id_0":"1","id_1":"10","id_2":"100","mean":0.0247748357508958},{"gid":132,"kilometers":370.68185689015,"id_0":"1","id_1":"13","id_2":"132","mean":0.143222562474279},{"gid":325,"kilometers":578.235407371319,"id_0":"1","id_1":"34","id_2":"325","mean":0.0824710530770418},{"gid":155,"kilometers":1703.80988971031,"id_0":"1","id_1":"15","id_2":"155","mean":0.152371785621892},{"gid":260,"kilometers":109.488275931729,"id_0":"1","id_1":"26","id_2":"260","mean":0.0608031322910406},{"gid":273,"kilometers":290.852884028746,"id_0":"1","id_1":"27","id_2":"273","mean":0.041024421516926},{"gid":35,"kilometers":524.075632429692,"id_0":"1","id_1":"4","id_2":"35","mean":0.0462741621076306},{"gid":256,"kilometers":1223.27075124554,"id_0":"1","id_1":"25","id_2":"256","mean":0.0258418248883864},{"gid":322,"kilometers":1147.56445610937,"id_0":"1","id_1":"34","id_2":"322","mean":0.0324381215738239},{"gid":54,"kilometers":952.509776523178,"id_0":"1","id_1":"6","id_2":"54","mean":0.0599787516647044},{"gid":28,"kilometers":472.716068907535,"id_0":"1","id_1":"3","id_2":"28","mean":0.0627374039612815},{"gid":172,"kilometers":86.3452167047955,"id_0":"1","id_1":"17","id_2":"172","mean":0.262792087210528},{"gid":244,"kilometers":511.346123944078,"id_0":"1","id_1":"25","id_2":"244","mean":0.0292051180211099},{"gid":4,"kilometers":605.953912442938,"id_0":"1","id_1":"1","id_2":"4","mean":0.0142765904842343},{"gid":150,"kilometers":50.161631249722,"id_0":"1","id_1":"14","id_2":"150","mean":0.153409219335795},{"gid":305,"kilometers":447.418829006148,"id_0":"1","id_1":"31","id_2":"305","mean":0.34099927320414},{"gid":295,"kilometers":256.991160818164,"id_0":"1","id_1":"31","id_2":"295","mean":0.178594282565579},{"gid":146,"kilometers":251.128979799612,"id_0":"1","id_1":"14","id_2":"146","mean":0.0123981922102683},{"gid":14,"kilometers":526.600859631705,"id_0":"1","id_1":"2","id_2":"14","mean":0.23187892635158},{"gid":27,"kilometers":1369.68943145958,"id_0":"1","id_1":"3","id_2":"27","mean":0.00649547084260833},{"gid":317,"kilometers":87.2273435313134,"id_0":"1","id_1":"33","id_2":"317","mean":0.0281074985762501},{"gid":249,"kilometers":59.4443659906173,"id_0":"1","id_1":"25","id_2":"249","mean":0.0103449815110471},{"gid":129,"kilometers":803.630066198859,"id_0":"1","id_1":"12","id_2":"129","mean":0.114071035495927},{"gid":165,"kilometers":2277.21571918777,"id_0":"1","id_1":"15","id_2":"165","mean":0.128199226548768},{"gid":240,"kilometers":945.916379036922,"id_0":"1","id_1":"24","id_2":"240","mean":0.0294076880607495},{"gid":22,"kilometers":303.458743260007,"id_0":"1","id_1":"3","id_2":"22","mean":0.367578224798421},{"gid":62,"kilometers":2489.19048642287,"id_0":"1","id_1":"7","id_2":"62","mean":0.0401104272925212},{"gid":326,"kilometers":1147.37103126497,"id_0":"1","id_1":"34","id_2":"326","mean":0.0507927245796864},{"gid":123,"kilometers":1191.3610771003,"id_0":"1","id_1":"12","id_2":"123","mean":0.0852569180554737},{"gid":118,"kilometers":2224.62499147684,"id_0":"1","id_1":"12","id_2":"118","mean":0.0990443665769175},{"gid":174,"kilometers":134.83130161018,"id_0":"1","id_1":"17","id_2":"174","mean":0.18603907555131},{"gid":115,"kilometers":900.411248283759,"id_0":"1","id_1":"12","id_2":"115","mean":0.0252086252014774},{"gid":158,"kilometers":483.294180142336,"id_0":"1","id_1":"15","id_2":"158","mean":0.112552823815324},{"gid":291,"kilometers":1005.05771913613,"id_0":"1","id_1":"30","id_2":"291","mean":null},{"gid":63,"kilometers":2473.88193417178,"id_0":"1","id_1":"7","id_2":"63","mean":0.0344632204285975},{"gid":221,"kilometers":64.2419382231489,"id_0":"1","id_1":"22","id_2":"221","mean":0.598179684277866},{"gid":166,"kilometers":103.460644302629,"id_0":"1","id_1":"16","id_2":"166","mean":0.0277579587704315},{"gid":204,"kilometers":216.298231103239,"id_0":"1","id_1":"20","id_2":"204","mean":0.33463099568491},{"gid":118,"kilometers":2224.62499147684,"id_0":"1","id_1":"12","id_2":"118","mean":0.103473613875555},{"gid":126,"kilometers":963.754671196585,"id_0":"1","id_1":"12","id_2":"126","mean":0.0645446207756604},{"gid":180,"kilometers":141.272839518683,"id_0":"1","id_1":"17","id_2":"180","mean":0.198028466233376},{"gid":184,"kilometers":262.425643605105,"id_0":"1","id_1":"18","id_2":"184","mean":0.105671962551111},{"gid":205,"kilometers":337.195385976917,"id_0":"1","id_1":"20","id_2":"205","mean":0.0914911939196224},{"gid":215,"kilometers":73.5862700952875,"id_0":"1","id_1":"22","id_2":"215","mean":0.410774770748202},{"gid":113,"kilometers":1903.1416790817,"id_0":"1","id_1":"11","id_2":"113","mean":0.0547752423129585},{"gid":240,"kilometers":945.916379036922,"id_0":"1","id_1":"24","id_2":"240","mean":0.023185431093304},{"gid":20,"kilometers":410.269311270445,"id_0":"1","id_1":"2","id_2":"20","mean":0.0977671175784345},{"gid":65,"kilometers":1608.86090585059,"id_0":"1","id_1":"7","id_2":"65","mean":0.0864182971111647},{"gid":268,"kilometers":43.290846044363,"id_0":"1","id_1":"26","id_2":"268","mean":0.0387067629173646},{"gid":208,"kilometers":135.34820097093,"id_0":"1","id_1":"21","id_2":"208","mean":0.116969076588539},{"gid":164,"kilometers":1668.94229789556,"id_0":"1","id_1":"15","id_2":"164","mean":0.082497943975779},{"gid":203,"kilometers":303.843283601822,"id_0":"1","id_1":"20","id_2":"203","mean":0.274642919827757},{"gid":2,"kilometers":1127.65759388239,"id_0":"1","id_1":"1","id_2":"2","mean":0.0212900857181964},{"gid":301,"kilometers":290.048612847926,"id_0":"1","id_1":"31","id_2":"301","mean":0.236213203578068},{"gid":95,"kilometers":1265.21848664937,"id_0":"1","id_1":"10","id_2":"95","mean":0.0144203844689565},{"gid":102,"kilometers":3621.42344649258,"id_0":"1","id_1":"11","id_2":"102","mean":0.0442715630640503},{"gid":17,"kilometers":217.747625514995,"id_0":"1","id_1":"2","id_2":"17","mean":null},{"gid":66,"kilometers":504.026030667192,"id_0":"1","id_1":"7","id_2":"66","mean":0.075007616718789},{"gid":168,"kilometers":68.5463301147509,"id_0":"1","id_1":"16","id_2":"168","mean":0.234577735850391},{"gid":49,"kilometers":435.854538134945,"id_0":"1","id_1":"5","id_2":"49","mean":0.0128162685220574},{"gid":33,"kilometers":192.465648395577,"id_0":"1","id_1":"4","id_2":"33","mean":0.385438692140181},{"gid":19,"kilometers":1236.05414574924,"id_0":"1","id_1":"2","id_2":"19","mean":0.0393253419377391},{"gid":137,"kilometers":493.089365715389,"id_0":"1","id_1":"13","id_2":"137","mean":0.124071090218122},{"gid":276,"kilometers":525.701334633467,"id_0":"1","id_1":"28","id_2":"276","mean":0.0215956280717855},{"gid":205,"kilometers":337.195385976917,"id_0":"1","id_1":"20","id_2":"205","mean":0.105781454064075},{"gid":186,"kilometers":188.618534429709,"id_0":"1","id_1":"18","id_2":"186","mean":0.0279919861346265},{"gid":252,"kilometers":144.391345848882,"id_0":"1","id_1":"25","id_2":"252","mean":0.0124345938707945},{"gid":184,"kilometers":262.425643605105,"id_0":"1","id_1":"18","id_2":"184","mean":0.41553586457253},{"gid":153,"kilometers":211.089675949987,"id_0":"1","id_1":"15","id_2":"153","mean":0.26340248883393},{"gid":234,"kilometers":8122.95826747167,"id_0":"1","id_1":"23","id_2":"234","mean":0.0445923250053104},{"gid":64,"kilometers":345.61248363829,"id_0":"1","id_1":"7","id_2":"64","mean":0.141603253401992},{"gid":130,"kilometers":378.92546454281,"id_0":"1","id_1":"13","id_2":"130","mean":0.132738090592252},{"gid":56,"kilometers":4196.90650689132,"id_0":"1","id_1":"7","id_2":"56","mean":0.0585833229209576},{"gid":199,"kilometers":619.410496648836,"id_0":"1","id_1":"19","id_2":"199","mean":0.308724219487198},{"gid":147,"kilometers":27.3237646007784,"id_0":"1","id_1":"14","id_2":"147","mean":0.130505309991205},{"gid":231,"kilometers":64.2328360027158,"id_0":"1","id_1":"22","id_2":"231","mean":0.453421861397978},{"gid":289,"kilometers":2510.34266323918,"id_0":"1","id_1":"30","id_2":"289","mean":0.0263571402544251},{"gid":67,"kilometers":695.782538477548,"id_0":"1","id_1":"8","id_2":"67","mean":0.20063033361631},{"gid":245,"kilometers":574.901802008144,"id_0":"1","id_1":"25","id_2":"245","mean":0.0420158139137025},{"gid":307,"kilometers":747.729708152773,"id_0":"1","id_1":"32","id_2":"307","mean":0.1195768152575},{"gid":176,"kilometers":51.2516673402192,"id_0":"1","id_1":"17","id_2":"176","mean":0.311642669497209},{"gid":36,"kilometers":520.357774633481,"id_0":"1","id_1":"4","id_2":"36","mean":0.124416229562704},{"gid":30,"kilometers":642.827822796675,"id_0":"1","id_1":"3","id_2":"30","mean":0.117223192300195},{"gid":118,"kilometers":2224.62499147684,"id_0":"1","id_1":"12","id_2":"118","mean":0.168332718027153},{"gid":101,"kilometers":1498.82505960676,"id_0":"1","id_1":"11","id_2":"101","mean":0.0510980793371177},{"gid":175,"kilometers":165.660733613924,"id_0":"1","id_1":"17","id_2":"175","mean":0.380401296800594},{"gid":156,"kilometers":553.057957590515,"id_0":"1","id_1":"15","id_2":"156","mean":0.0736289941856486},{"gid":183,"kilometers":194.801883950795,"id_0":"1","id_1":"17","id_2":"183","mean":0.483804375564889},{"gid":309,"kilometers":584.172542793481,"id_0":"1","id_1":"32","id_2":"309","mean":0.164565835024924},{"gid":61,"kilometers":458.570387912424,"id_0":"1","id_1":"7","id_2":"61","mean":0.081724004340912},{"gid":145,"kilometers":15.6090259846347,"id_0":"1","id_1":"14","id_2":"145","mean":0.111446708393424},{"gid":267,"kilometers":336.11913816342,"id_0":"1","id_1":"26","id_2":"267","mean":0.0132428614561629},{"gid":69,"kilometers":1106.89591598631,"id_0":"1","id_1":"8","id_2":"69","mean":null},{"gid":81,"kilometers":341.895975514414,"id_0":"1","id_1":"9","id_2":"81","mean":0.080579008937053},{"gid":43,"kilometers":25.5425939302065,"id_0":"1","id_1":"4","id_2":"43","mean":0.534647821609029},{"gid":175,"kilometers":165.660733613924,"id_0":"1","id_1":"17","id_2":"175","mean":0.348558355770912},{"gid":248,"kilometers":150.886091906805,"id_0":"1","id_1":"25","id_2":"248","mean":0.0577023447596692},{"gid":37,"kilometers":585.18838392882,"id_0":"1","id_1":"4","id_2":"37","mean":0.155844049824878},{"gid":117,"kilometers":2479.35215715068,"id_0":"1","id_1":"12","id_2":"117","mean":0.0674599477692196},{"gid":79,"kilometers":428.667050852766,"id_0":"1","id_1":"9","id_2":"79","mean":0.0481462325194483},{"gid":310,"kilometers":778.811594485114,"id_0":"1","id_1":"32","id_2":"310","mean":0.0700188675541591},{"gid":209,"kilometers":313.391279575036,"id_0":"1","id_1":"21","id_2":"209","mean":0.0161960387091228},{"gid":200,"kilometers":293.765748366153,"id_0":"1","id_1":"19","id_2":"200","mean":0.350141658633865},{"gid":189,"kilometers":203.79839460026,"id_0":"1","id_1":"18","id_2":"189","mean":0.0296252138801962},{"gid":269,"kilometers":45.7579705662129,"id_0":"1","id_1":"26","id_2":"269","mean":0.0181844712477762},{"gid":133,"kilometers":303.201763646335,"id_0":"1","id_1":"13","id_2":"133","mean":0.0723768471913322},{"gid":118,"kilometers":2224.62499147684,"id_0":"1","id_1":"12","id_2":"118","mean":0.136954608877187},{"gid":23,"kilometers":622.998934961342,"id_0":"1","id_1":"3","id_2":"23","mean":0.247308602368962},{"gid":218,"kilometers":140.69525942264,"id_0":"1","id_1":"22","id_2":"218","mean":0.440055105372661},{"gid":165,"kilometers":2277.21571918777,"id_0":"1","id_1":"15","id_2":"165","mean":0.157449114739664},{"gid":243,"kilometers":301.655706668996,"id_0":"1","id_1":"24","id_2":"243","mean":0.0222385610203196},{"gid":57,"kilometers":940.374646584695,"id_0":"1","id_1":"7","id_2":"57","mean":0.0696128515186121},{"gid":121,"kilometers":607.555101909951,"id_0":"1","id_1":"12","id_2":"121","mean":0.238611306298429},{"gid":108,"kilometers":646.358660747089,"id_0":"1","id_1":"11","id_2":"108","mean":0.160504432474663},{"gid":8,"kilometers":1744.45893028534,"id_0":"1","id_1":"1","id_2":"8","mean":0.016673256341999},{"gid":308,"kilometers":956.951707373191,"id_0":"1","id_1":"32","id_2":"308","mean":0.0280712576953618},{"gid":70,"kilometers":1045.26281484687,"id_0":"1","id_1":"8","id_2":"70","mean":0.181963531120026},{"gid":328,"kilometers":662.595222920105,"id_0":"1","id_1":"34","id_2":"328","mean":0.150810676695147},{"gid":5,"kilometers":1355.25759722949,"id_0":"1","id_1":"1","id_2":"5","mean":0.0190164293937869},{"gid":17,"kilometers":217.747625514995,"id_0":"1","id_1":"2","id_2":"17","mean":0.304952696935851},{"gid":124,"kilometers":715.924605128269,"id_0":"1","id_1":"12","id_2":"124","mean":0.083442080794123},{"gid":125,"kilometers":1193.58975307915,"id_0":"1","id_1":"12","id_2":"125","mean":0.112791545364751},{"gid":54,"kilometers":952.509776523178,"id_0":"1","id_1":"6","id_2":"54","mean":0.0508461634951132},{"gid":166,"kilometers":103.460644302629,"id_0":"1","id_1":"16","id_2":"166","mean":0.0333546603889351},{"gid":202,"kilometers":810.552327306044,"id_0":"1","id_1":"19","id_2":"202","mean":0.265906953156013},{"gid":133,"kilometers":303.201763646335,"id_0":"1","id_1":"13","id_2":"133","mean":null},{"gid":39,"kilometers":333.463126869507,"id_0":"1","id_1":"4","id_2":"39","mean":0.137117969565215},{"gid":160,"kilometers":1099.16637486725,"id_0":"1","id_1":"15","id_2":"160","mean":0.0905072719434606},{"gid":77,"kilometers":942.38395970543,"id_0":"1","id_1":"8","id_2":"77","mean":0.0846297518247001},{"gid":271,"kilometers":837.038960701354,"id_0":"1","id_1":"27","id_2":"271","mean":0.0229774642965919},{"gid":19,"kilometers":1236.05414574924,"id_0":"1","id_1":"2","id_2":"19","mean":0.141107638037975},{"gid":53,"kilometers":1396.7589687969,"id_0":"1","id_1":"6","id_2":"53","mean":0.0112204623412246},{"gid":242,"kilometers":702.31154284394,"id_0":"1","id_1":"24","id_2":"242","mean":0.0238734617003145},{"gid":237,"kilometers":312.654297396599,"id_0":"1","id_1":"23","id_2":"237","mean":0.0764302654188132},{"gid":304,"kilometers":1104.51412215948,"id_0":"1","id_1":"31","id_2":"304","mean":0.0238442041835365},{"gid":53,"kilometers":1396.7589687969,"id_0":"1","id_1":"6","id_2":"53","mean":0.0440628398216916},{"gid":284,"kilometers":1567.74726538161,"id_0":"1","id_1":"29","id_2":"284","mean":0.0226024237209189},{"gid":82,"kilometers":250.411992554445,"id_0":"1","id_1":"9","id_2":"82","mean":0.0508004273989157},{"gid":117,"kilometers":2479.35215715068,"id_0":"1","id_1":"12","id_2":"117","mean":0.0632046416791164},{"gid":201,"kilometers":251.305044792168,"id_0":"1","id_1":"19","id_2":"201","mean":0.412806569839467},{"gid":254,"kilometers":211.911263869857,"id_0":"1","id_1":"25","id_2":"254","mean":0.024854517165125},{"gid":59,"kilometers":1013.20275109843,"id_0":"1","id_1":"7","id_2":"59","mean":0.0879441527284675},{"gid":281,"kilometers":143.985700529837,"id_0":"1","id_1":"28","id_2":"281","mean":0.0157038744094265},{"gid":255,"kilometers":1233.39033665643,"id_0":"1","id_1":"25","id_2":"255","mean":0.025126353832466},{"gid":163,"kilometers":1416.89203085407,"id_0":"1","id_1":"15","id_2":"163","mean":0.129724250417075},{"gid":211,"kilometers":416.240861907018,"id_0":"1","id_1":"21","id_2":"211","mean":0.047705941173916},{"gid":314,"kilometers":361.395270538346,"id_0":"1","id_1":"33","id_2":"314","mean":0.0165589325229938},{"gid":128,"kilometers":2852.2392097553,"id_0":"1","id_1":"12","id_2":"128","mean":0.0699905041317638},{"gid":207,"kilometers":391.329296076648,"id_0":"1","id_1":"20","id_2":"207","mean":0.243734829777337},{"gid":13,"kilometers":768.052168731602,"id_0":"1","id_1":"1","id_2":"13","mean":0.010483382682568},{"gid":55,"kilometers":1442.83837421058,"id_0":"1","id_1":"6","id_2":"55","mean":0.0199914396905122},{"gid":165,"kilometers":2277.21571918777,"id_0":"1","id_1":"15","id_2":"165","mean":0.19106205895455},{"gid":304,"kilometers":1104.51412215948,"id_0":"1","id_1":"31","id_2":"304","mean":0.0236261732407856},{"gid":52,"kilometers":3122.05084188944,"id_0":"1","id_1":"6","id_2":"52","mean":0.0192764208060568},{"gid":183,"kilometers":194.801883950795,"id_0":"1","id_1":"17","id_2":"183","mean":null},{"gid":275,"kilometers":96.6434263941868,"id_0":"1","id_1":"28","id_2":"275","mean":0.396205968566351},{"gid":281,"kilometers":143.985700529837,"id_0":"1","id_1":"28","id_2":"281","mean":0.024312379228269},{"gid":44,"kilometers":647.871582790161,"id_0":"1","id_1":"4","id_2":"44","mean":0.152089232185587},{"gid":228,"kilometers":123.045917984758,"id_0":"1","id_1":"22","id_2":"228","mean":0.182745694396903},{"gid":294,"kilometers":228.186520185013,"id_0":"1","id_1":"31","id_2":"294","mean":0.266300157518836},{"gid":320,"kilometers":972.036491528335,"id_0":"1","id_1":"34","id_2":"320","mean":0.0792196521371346},{"gid":225,"kilometers":193.596450359636,"id_0":"1","id_1":"22","id_2":"225","mean":0.348681734145225},{"gid":230,"kilometers":215.1102667214,"id_0":"1","id_1":"22","id_2":"230","mean":0.329738392886587},{"gid":302,"kilometers":623.085136077794,"id_0":"1","id_1":"31","id_2":"302","mean":0.176680648942153},{"gid":51,"kilometers":2400.91413929585,"id_0":"1","id_1":"5","id_2":"51","mean":0.00740210016852236},{"gid":197,"kilometers":488.40049671053,"id_0":"1","id_1":"19","id_2":"197","mean":0.269007028950083},{"gid":235,"kilometers":508.115942182882,"id_0":"1","id_1":"23","id_2":"235","mean":null},{"gid":172,"kilometers":86.3452167047955,"id_0":"1","id_1":"17","id_2":"172","mean":0.335398199186994},{"gid":20,"kilometers":410.269311270445,"id_0":"1","id_1":"2","id_2":"20","mean":0.514193855112809},{"gid":169,"kilometers":67.7867903303567,"id_0":"1","id_1":"16","id_2":"169","mean":0.302705684647507},{"gid":27,"kilometers":1369.68943145958,"id_0":"1","id_1":"3","id_2":"27","mean":0.0109186980724609},{"gid":235,"kilometers":508.115942182882,"id_0":"1","id_1":"23","id_2":"235","mean":0.153984002826384},{"gid":302,"kilometers":623.085136077794,"id_0":"1","id_1":"31","id_2":"302","mean":0.115282950935659},{"gid":206,"kilometers":234.791516940729,"id_0":"1","id_1":"20","id_2":"206","mean":0.184538435799193},{"gid":193,"kilometers":81.7055112633344,"id_0":"1","id_1":"18","id_2":"193","mean":0.492563738009354},{"gid":85,"kilometers":335.38312015953,"id_0":"1","id_1":"9","id_2":"85","mean":0.0528715235151601},{"gid":207,"kilometers":391.329296076648,"id_0":"1","id_1":"20","id_2":"207","mean":0.395653675553373},{"gid":223,"kilometers":222.850660353271,"id_0":"1","id_1":"22","id_2":"223","mean":null},{"gid":89,"kilometers":319.158903315955,"id_0":"1","id_1":"9","id_2":"89","mean":0.0276432387245039},{"gid":299,"kilometers":246.175426835359,"id_0":"1","id_1":"31","id_2":"299","mean":0.17731911042493},{"gid":40,"kilometers":1379.83684832583,"id_0":"1","id_1":"4","id_2":"40","mean":0.12257434601339},{"gid":232,"kilometers":146.527842956699,"id_0":"1","id_1":"22","id_2":"232","mean":0.541104186751714},{"gid":227,"kilometers":40.0452461002614,"id_0":"1","id_1":"22","id_2":"227","mean":0.350629972377851},{"gid":3,"kilometers":1135.03766752925,"id_0":"1","id_1":"1","id_2":"3","mean":0.0824969723378482},{"gid":157,"kilometers":242.680166184771,"id_0":"1","id_1":"15","id_2":"157","mean":0.101433618289978},{"gid":18,"kilometers":1817.93558526618,"id_0":"1","id_1":"2","id_2":"18","mean":0.215331806699435},{"gid":6,"kilometers":769.854131554713,"id_0":"1","id_1":"1","id_2":"6","mean":0.0415575479067265},{"gid":247,"kilometers":1553.05023354795,"id_0":"1","id_1":"25","id_2":"247","mean":0.0260067234449788},{"gid":120,"kilometers":13.2729650668111,"id_0":"1","id_1":"12","id_2":"120","mean":0.50616957190561},{"gid":10,"kilometers":348.748317460755,"id_0":"1","id_1":"1","id_2":"10","mean":0.0585613806128792},{"gid":157,"kilometers":242.680166184771,"id_0":"1","id_1":"15","id_2":"157","mean":0.546556239094848},{"gid":234,"kilometers":8122.95826747167,"id_0":"1","id_1":"23","id_2":"234","mean":0.0586275747224059},{"gid":42,"kilometers":159.561567052624,"id_0":"1","id_1":"4","id_2":"42","mean":0.0808635903558962},{"gid":191,"kilometers":56.5364329621794,"id_0":"1","id_1":"18","id_2":"191","mean":0.392315576259703},{"gid":60,"kilometers":2710.4455142914,"id_0":"1","id_1":"7","id_2":"60","mean":0.0540236002382431},{"gid":68,"kilometers":277.359631209563,"id_0":"1","id_1":"8","id_2":"68","mean":0.147062242264734},{"gid":69,"kilometers":1106.89591598631,"id_0":"1","id_1":"8","id_2":"69","mean":0.0328413563270708},{"gid":259,"kilometers":321.102657260243,"id_0":"1","id_1":"26","id_2":"259","mean":0.0156253750419449},{"gid":14,"kilometers":526.600859631705,"id_0":"1","id_1":"2","id_2":"14","mean":0.109241992663347},{"gid":196,"kilometers":286.082079926559,"id_0":"1","id_1":"19","id_2":"196","mean":0.269939580488847},{"gid":230,"kilometers":215.1102667214,"id_0":"1","id_1":"22","id_2":"230","mean":0.0974915426033983},{"gid":234,"kilometers":8122.95826747167,"id_0":"1","id_1":"23","id_2":"234","mean":null},{"gid":135,"kilometers":328.923054762575,"id_0":"1","id_1":"13","id_2":"135","mean":0.149594213297024},{"gid":233,"kilometers":3942.5313584559,"id_0":"1","id_1":"23","id_2":"233","mean":0.0343345233140295},{"gid":134,"kilometers":741.190630122654,"id_0":"1","id_1":"13","id_2":"134","mean":0.059306474441554},{"gid":155,"kilometers":1703.80988971031,"id_0":"1","id_1":"15","id_2":"155","mean":0.130634617224046},{"gid":9,"kilometers":711.305441571042,"id_0":"1","id_1":"1","id_2":"9","mean":0.0402714571514385},{"gid":111,"kilometers":3280.28491255369,"id_0":"1","id_1":"11","id_2":"111","mean":0.064021480915119},{"gid":251,"kilometers":275.673298009373,"id_0":"1","id_1":"25","id_2":"251","mean":0.0127012227328084},{"gid":178,"kilometers":183.112570172495,"id_0":"1","id_1":"17","id_2":"178","mean":0.109668353338204},{"gid":136,"kilometers":381.533773748992,"id_0":"1","id_1":"13","id_2":"136","mean":0.0624753201215782},{"gid":262,"kilometers":277.050875893368,"id_0":"1","id_1":"26","id_2":"262","mean":0.0245402069014901},{"gid":88,"kilometers":492.553282781882,"id_0":"1","id_1":"9","id_2":"88","mean":0.0113728595276488},{"gid":177,"kilometers":78.9120070168167,"id_0":"1","id_1":"17","id_2":"177","mean":0.0939052061842724}] 2 | --------------------------------------------------------------------------------