├── .node-version ├── tests ├── samples │ ├── 1 │ │ ├── calendar_dates.txt │ │ ├── trips.txt │ │ ├── transfers.txt │ │ ├── frequencies.txt │ │ ├── calendar.txt │ │ ├── shapes.txt │ │ ├── feed_info.txt │ │ ├── stops.txt │ │ ├── modes.txt │ │ ├── stop_times.txt │ │ ├── agency.txt │ │ ├── pathways.txt │ │ └── routes.txt │ ├── 2 │ │ └── stops.txt │ └── 3 │ │ └── stops.txt ├── .eslintrc ├── test_feed_info.js ├── test_agencies.js ├── test_stops.js ├── test_calendars.js ├── test_trips.js ├── test_transfers.js ├── test_routes.js ├── test_pathways.js ├── test_frequencies.js ├── test_generic_table_functions.js ├── test_fare_rules.js ├── test_gtfs_options.js ├── test_shapes.js ├── test_fare_attributes.js ├── test_calendar_dates.js ├── test_stop_times.js └── tests.js ├── .eslintrc ├── .idea ├── encodings.xml ├── misc.xml ├── vcs.xml ├── jsLibraryMappings.xml ├── inspectionProfiles │ └── Project_Default.xml ├── modules.xml ├── gtfsNodeLib.iml └── runConfigurations │ └── Tests.xml ├── index.js ├── LICENSE ├── helpers ├── logging_iterator_wrapper.js ├── schema.js ├── export.js ├── import.js └── getters.js ├── package.json ├── .gitignore └── README.md /.node-version: -------------------------------------------------------------------------------- 1 | 12.14.0 2 | -------------------------------------------------------------------------------- /tests/samples/1/calendar_dates.txt: -------------------------------------------------------------------------------- 1 | service_id,date,exception_type 2 | service_0,20171228,1 3 | service_0,20171231,2 4 | -------------------------------------------------------------------------------- /tests/samples/1/trips.txt: -------------------------------------------------------------------------------- 1 | route_id,service_id,trip_id,trip_headsign,shape_id 2 | route_0,service_0,trip_0,Trip 0,shape_0 3 | -------------------------------------------------------------------------------- /tests/samples/1/transfers.txt: -------------------------------------------------------------------------------- 1 | from_stop_id,to_stop_id,transfer_type,min_transfer_time 2 | stop_0,stop_1,0, 3 | stop_1,stop_0,1, 4 | -------------------------------------------------------------------------------- /tests/samples/1/frequencies.txt: -------------------------------------------------------------------------------- 1 | trip_id,start_time,end_time,headway_secs,exact_times 2 | trip_0,10:00:00,15:00:00,600, 3 | trip_0,15:00:00,20:00:00,1200, 4 | -------------------------------------------------------------------------------- /tests/samples/1/calendar.txt: -------------------------------------------------------------------------------- 1 | service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date 2 | service_0,1,1,1,1,1,1,1,20000101,21001231 -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "transit", 3 | "rules": { 4 | "no-console": 0 5 | }, 6 | "globals": { 7 | "describe": true, 8 | "it": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/samples/1/shapes.txt: -------------------------------------------------------------------------------- 1 | shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence,shape_dist_traveled 2 | shape_0,37.728631,-122.431282,1,0 3 | shape_0,37.74103,-122.422482,2,10 4 | -------------------------------------------------------------------------------- /tests/samples/1/feed_info.txt: -------------------------------------------------------------------------------- 1 | feed_publisher_name,feed_publisher_url,feed_lang,feed_start_date,feed_end_date,feed_version 2 | Publisher Name,http://google.com,en,20000101,21001231,42 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/samples/1/stops.txt: -------------------------------------------------------------------------------- 1 | stop_id,stop_code,stop_name,stop_desc,stop_lat,stop_lon 2 | stop_0,SC0,Stop 0,Some stop,37.728631,-122.431282 3 | stop_1,SC1,Stop 1,Some other stop,37.74103,-122.422482 4 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/samples/1/modes.txt: -------------------------------------------------------------------------------- 1 | mode_id,mode_name,mode_url,mode_timezone,mode_lang,mode_phone,mode_fare_url,mode_email 2 | mode_0,mode 0,http://google.com,America/New_York,en,(310) 555-0222,http://google.com,contact@google.com 3 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/samples/1/stop_times.txt: -------------------------------------------------------------------------------- 1 | trip_id,arrival_time,departure_time,stop_id,stop_sequence,pickup_type,drop_off_type,stop_headsign 2 | trip_0,10:00:00,10:00:00,stop_0,0,,,Stop Headsign 0 3 | trip_0,20:00:00,20:00:00,stop_1,1,,,Stop Headsign 1 -------------------------------------------------------------------------------- /tests/samples/2/stops.txt: -------------------------------------------------------------------------------- 1 | stop_id,stop_code,stop_name,stop_desc,stop_lat,stop_lon 2 | stop_0,SC0,Stop 0,Some "other" stop,37.728631,-122.431282 3 | stop_1,SC1,Stop 1,Some stop,37.74103,-122.422482, 4 | stop_2,SC2,Stop 2,Some stop,37.74103 5 | -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "import/no-extraneous-dependencies": ["error", {"devDependencies": true}], 4 | "global-require": "off" 5 | }, 6 | "env": { 7 | "mocha": true, 8 | "node": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/samples/1/agency.txt: -------------------------------------------------------------------------------- 1 | agency_id,agency_name,agency_url,agency_timezone,agency_lang,agency_phone,agency_fare_url,agency_email 2 | agency_0,Agency 0,http://google.com,America/New_York,en,(310) 555-0222,http://google.com,contact@google.com 3 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/samples/1/pathways.txt: -------------------------------------------------------------------------------- 1 | pathway_id,from_stop_id,to_stop_id,pathway_mode,is_bidirectional,length,traversal_time,stair_count,max_slope,min_width,signposted_as,reversed_signposted_as 2 | 1,stop_0,stop_1,1,0,10,72,12,,,sign_posted_as,reversed_sign_posted_as 3 | 2,stop_2,stop_3,2,0,10,72,12,,,sign_posted_as,reversed_sign_posted_as 4 | -------------------------------------------------------------------------------- /tests/samples/1/routes.txt: -------------------------------------------------------------------------------- 1 | agency_id,route_id,route_short_name,route_long_name,route_type,tts_route_short_name,tts_route_long_name 2 | agency_0,route_0,R0,Route 0,3,r0,rooouuuteee 0 3 | agency_0,route_x,RX,"""Route X""",3,, 4 | agency_0,route_utf8,RÛTF8,route_😎êωn → ∞⠁⠧⠑ ⠼éöÿΚαλημέρα'´`,3,旱獺屬, 5 | agency_0, route_y, RY,"{""routeLongName"":""""}",3,, 6 | -------------------------------------------------------------------------------- /.idea/gtfsNodeLib.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const infoLog = require('debug')('gtfsNodeLib:i'); 4 | const warningLog = require('debug')('gtfsNodeLib:w'); 5 | 6 | const Gtfs = require('./gtfs'); 7 | 8 | /* Fallback to replace Transit's internal notice system */ 9 | 10 | if (process.notices === undefined) { 11 | process.notices = { 12 | addInfo: (title, content) => { infoLog(`[Info] ${title}:\n${content}`); }, 13 | addWarning: (title, content) => { warningLog(`[Warning] ${title}:\n${content}`); }, 14 | }; 15 | } 16 | 17 | module.exports = { 18 | Gtfs, 19 | }; 20 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Tests.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | project 4 | 5 | $PROJECT_DIR$ 6 | true 7 | bdd 8 | 9 | DIRECTORY 10 | $PROJECT_DIR$/tests 11 | false 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/test_feed_info.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { expect } = require('chai'); 4 | const { Gtfs } = require('../index'); 5 | 6 | describe('Tests on GTFS feed info', () => { 7 | it('Tests on gtfs.getFeedInfo()', (done) => { 8 | const path = `${__dirname}/samples/1`; 9 | const gtfs = new Gtfs(path); 10 | 11 | expect(gtfs.getFeedInfo().feed_lang).to.equal('en'); 12 | 13 | done(); 14 | }); 15 | 16 | it('Tests on gtfs.setFeedInfo(feedInfo)', (done) => { 17 | const path = `${__dirname}/samples/1`; 18 | const gtfs = new Gtfs(path); 19 | 20 | gtfs.setFeedInfo({ 21 | feed_publisher_name: 'Some other name', 22 | feed_publisher_url: 'http://google.ca', 23 | feed_lang: 'en-CA', 24 | }); 25 | 26 | expect(gtfs.getFeedInfo().feed_lang).to.equal('en-CA'); 27 | 28 | done(); 29 | }); 30 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Transit App 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /helpers/logging_iterator_wrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const infoLog = require('debug')('gtfsNodeLib:i'); 4 | 5 | module.exports = (prefix, valueByKey, iteratee) => { 6 | if ( 7 | valueByKey instanceof Array === false && 8 | valueByKey instanceof Map === false && 9 | valueByKey instanceof Set === false 10 | ) { 11 | throw new Error('valueByKey should be an Array, a Map or a Set.'); 12 | } 13 | 14 | let lastLogAt = Date.now(); 15 | let numberOfKeysDone = 0; 16 | let interval = 2000; 17 | let oneProgressionLogHasBeenPrinted = false; 18 | 19 | valueByKey.forEach((value, key) => { 20 | iteratee(value, key); 21 | 22 | numberOfKeysDone += 1; 23 | 24 | if (!process.env.TEST && Date.now() - lastLogAt > interval) { 25 | const numberOfTotalKeys = valueByKey instanceof Array ? valueByKey.length : valueByKey.size; 26 | const percentageDone = (numberOfKeysDone / numberOfTotalKeys) * 100; 27 | 28 | infoLog(`[${prefix}] ${percentageDone.toPrecision(2)}% done`); 29 | 30 | lastLogAt = Date.now(); 31 | oneProgressionLogHasBeenPrinted = true; 32 | interval = (interval < 10000) ? interval + 2000 : 10000; 33 | } 34 | }); 35 | 36 | if (oneProgressionLogHasBeenPrinted && process.env.TEST === undefined) { 37 | infoLog(`[${prefix}] Done`); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@transit/gtfs", 3 | "version": "4.1.0", 4 | "description": "A Node.js library for GTFS", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha tests/*", 8 | "publish-major": "npm version major && npm publish --access public && git push --tags", 9 | "publish-minor": "npm version minor && npm publish --access public && git push --tags", 10 | "publish-patch": "npm version patch && npm publish --access public && git push --tags" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/TransitApp/gtfsNodeLib.git" 15 | }, 16 | "keywords": [ 17 | "GTFS", 18 | "Transit" 19 | ], 20 | "author": "Transit Inc.", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/TransitApp/gtfsNodeLib/issues" 24 | }, 25 | "homepage": "https://github.com/TransitApp/gtfsNodeLib#readme", 26 | "dependencies": { 27 | "async": "2.6.4", 28 | "debug": "3.1.0", 29 | "fs-extra": "5.0.0", 30 | "papaparse": "^5.2.0" 31 | }, 32 | "jshintConfig": { 33 | "esversion": 6, 34 | "node": true, 35 | "globals": { 36 | "__rootname": false 37 | } 38 | }, 39 | "devDependencies": { 40 | "chai": "^3.5.0", 41 | "eslint": "^4.18.2", 42 | "eslint-config-transit": "^1.0.3", 43 | "eslint-plugin-import": "^2.2.0", 44 | "mocha": "^5.2.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/test_agencies.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { expect } = require('chai'); 4 | const { Gtfs } = require('../index'); 5 | 6 | describe('Tests on GTFS agencies', () => { 7 | it('Tests on agencies functions', (done) => { 8 | const path = `${__dirname}/samples/1`; 9 | const gtfs = new Gtfs(path); 10 | 11 | expect(sortedKeys(gtfs.getIndexedAgencies())).to.deep.equal(['agency_0']); 12 | 13 | const agency0 = gtfs.getAgencyWithId('agency_0'); 14 | expect(agency0.agency_name).to.equal('Agency 0'); 15 | 16 | gtfs.addAgency({agency_id: 'agency_1', agency_name: 'Agency 1'}); 17 | expect(sortedKeys(gtfs.getIndexedAgencies())).to.deep.equal(['agency_0', 'agency_1']); 18 | 19 | gtfs.addAgencies([ 20 | {agency_id: 'agency_2', agency_name: 'Agency 2'}, 21 | {agency_id: 'agency_3', agency_name: 'Agency 3'}, 22 | ]); 23 | expect(sortedKeys(gtfs.getIndexedAgencies())).to.deep.equal(['agency_0', 'agency_1', 'agency_2', 'agency_3']); 24 | 25 | gtfs.removeAgency(gtfs.getAgencyWithId('agency_2')); 26 | expect(sortedKeys(gtfs.getIndexedAgencies())).to.deep.equal(['agency_0', 'agency_1', 'agency_3']); 27 | 28 | gtfs.removeAgencies([gtfs.getAgencyWithId('agency_0'), gtfs.getAgencyWithId('agency_3')]); 29 | expect(sortedKeys(gtfs.getIndexedAgencies())).to.deep.equal(['agency_1']); 30 | 31 | gtfs.setIndexedAgencies(new Map([['agency_0', agency0]])); 32 | expect(sortedKeys(gtfs.getIndexedAgencies())).to.deep.equal(['agency_0']); 33 | 34 | const agencyIds = []; 35 | gtfs.forEachAgency((agency) => { 36 | agencyIds.push(agency.agency_id); 37 | }); 38 | expect(agencyIds).to.deep.equal(['agency_0']); 39 | 40 | const route0 = gtfs.getRouteWithId('route_0'); 41 | const agencyOfRoute0 = gtfs.getAgencyOfRoute(route0); 42 | expect(agencyOfRoute0.agency_name).to.deep.equal('Agency 0'); 43 | 44 | done(); 45 | }); 46 | 47 | it('Tests on gtfs.resetAgencies()', (done) => { 48 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 49 | 50 | expect(gtfs.getIndexedAgencies().size).to.equal(1); 51 | 52 | gtfs.resetAgencies(); 53 | 54 | expect(gtfs.getIndexedAgencies().size).to.equal(0); 55 | 56 | done(); 57 | }); 58 | }); 59 | 60 | function sortedKeys(map) { 61 | return Array.from(map.keys()).sort(); 62 | } 63 | -------------------------------------------------------------------------------- /tests/test_stops.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { expect } = require('chai'); 4 | const { Gtfs } = require('../index'); 5 | 6 | describe('Tests on GTFS stops', () => { 7 | it('Tests on stops functions', (done) => { 8 | const path = `${__dirname}/samples/1`; 9 | const gtfs = new Gtfs(path); 10 | 11 | expect(sortedKeys(gtfs.getIndexedStops())).to.deep.equal(['stop_0', 'stop_1']); 12 | 13 | const stop0 = gtfs.getStopWithId('stop_0'); 14 | const stop1 = gtfs.getStopWithId('stop_1'); 15 | expect(stop0.stop_name).to.equal('Stop 0'); 16 | expect(stop1.stop_name).to.equal('Stop 1'); 17 | 18 | gtfs.addStop({ stop_id: 'stop_2', stop_name: 'Stop 2' }); 19 | expect(sortedKeys(gtfs.getIndexedStops())).to.deep.equal(['stop_0', 'stop_1', 'stop_2']); 20 | 21 | gtfs.addStops([ 22 | { stop_id: 'stop_3', stop_name: 'Stop 3' }, 23 | { stop_id: 'stop_4', stop_name: 'Stop 4' }, 24 | ]); 25 | expect(sortedKeys(gtfs.getIndexedStops())).to.deep.equal(['stop_0', 'stop_1', 'stop_2', 'stop_3', 'stop_4']); 26 | 27 | gtfs.removeStop(gtfs.getStopWithId('stop_2')); 28 | expect(sortedKeys(gtfs.getIndexedStops())).to.deep.equal(['stop_0', 'stop_1', 'stop_3', 'stop_4']); 29 | 30 | gtfs.removeStops([gtfs.getStopWithId('stop_1'), gtfs.getStopWithId('stop_3')]); 31 | expect(sortedKeys(gtfs.getIndexedStops())).to.deep.equal(['stop_0', 'stop_4']); 32 | 33 | gtfs.setIndexedStops(new Map([['stop_0', stop0], ['stop_1', stop1]])); 34 | expect(sortedKeys(gtfs.getIndexedStops())).to.deep.equal(['stop_0', 'stop_1']); 35 | 36 | const stopIds = []; 37 | gtfs.forEachStop((stop) => { 38 | stopIds.push(stop.stop_id); 39 | }); 40 | expect(stopIds.sort()).to.deep.equal(['stop_0', 'stop_1']); 41 | 42 | const stopTime00 = gtfs.getStopTimeWithTripIdAndStopSequence('trip_0', '0'); 43 | const stopOfStopTime00 = gtfs.getStopOfStopTime(stopTime00); 44 | expect(stopOfStopTime00.stop_name).to.equal('Stop 0'); 45 | 46 | done(); 47 | }); 48 | 49 | it('Tests on gtfs.resetStops()', (done) => { 50 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 51 | 52 | expect(gtfs.getIndexedStops().size).to.equal(2); 53 | 54 | gtfs.resetStops(); 55 | 56 | expect(gtfs.getIndexedStops().size).to.equal(0); 57 | 58 | done(); 59 | }); 60 | }); 61 | 62 | function sortedKeys(map) { 63 | return Array.from(map.keys()).sort(); 64 | } 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (http://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # Typescript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # VS code configuration 62 | .vscode 63 | 64 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 65 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 66 | 67 | # User-specific stuff 68 | .idea/**/workspace.xml 69 | .idea/**/tasks.xml 70 | .idea/**/usage.statistics.xml 71 | .idea/**/dictionaries 72 | .idea/**/shelf 73 | 74 | # Sensitive or high-churn files 75 | .idea/**/dataSources/ 76 | .idea/**/dataSources.ids 77 | .idea/**/dataSources.local.xml 78 | .idea/**/sqlDataSources.xml 79 | .idea/**/dynamic.xml 80 | .idea/**/uiDesigner.xml 81 | .idea/**/dbnavigator.xml 82 | 83 | # Gradle 84 | .idea/**/gradle.xml 85 | .idea/**/libraries 86 | 87 | # CMake 88 | cmake-build-*/ 89 | 90 | # Mongo Explorer plugin 91 | .idea/**/mongoSettings.xml 92 | 93 | # File-based project format 94 | *.iws 95 | 96 | # IntelliJ 97 | out/ 98 | 99 | # mpeltonen/sbt-idea plugin 100 | .idea_modules/ 101 | 102 | # JIRA plugin 103 | atlassian-ide-plugin.xml 104 | 105 | # Cursive Clojure plugin 106 | .idea/replstate.xml 107 | 108 | # Crashlytics plugin (for Android Studio and IntelliJ) 109 | com_crashlytics_export_strings.xml 110 | crashlytics.properties 111 | crashlytics-build.properties 112 | fabric.properties 113 | 114 | # Editor-based Rest Client 115 | .idea/httpRequests 116 | -------------------------------------------------------------------------------- /tests/test_calendars.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { expect } = require('chai'); 4 | const { Gtfs } = require('../index'); 5 | 6 | describe('Tests on GTFS calendars', () => { 7 | it('Tests on calendars functions', (done) => { 8 | const path = `${__dirname}/samples/1`; 9 | const gtfs = new Gtfs(path); 10 | 11 | expect(sortedKeys(gtfs.getIndexedCalendars())).to.deep.equal(['service_0']); 12 | 13 | const calendar0 = gtfs.getCalendarWithServiceId('service_0'); 14 | expect(calendar0.start_date).to.equal('20000101'); 15 | 16 | gtfs.addCalendar({ service_id: 'service_1', start_date: '20010101', end_date: '20010101' }); 17 | expect(sortedKeys(gtfs.getIndexedCalendars())).to.deep.equal(['service_0', 'service_1']); 18 | 19 | gtfs.addCalendars([ 20 | { service_id: 'service_2', start_date: '20020101', end_date: '20020101' }, 21 | { service_id: 'service_3', start_date: '20030101', end_date: '20030101' }, 22 | ]); 23 | expect(sortedKeys(gtfs.getIndexedCalendars())).to.deep.equal(['service_0', 'service_1', 'service_2', 'service_3']); 24 | 25 | gtfs.removeCalendar(gtfs.getCalendarWithServiceId('service_2')); 26 | expect(sortedKeys(gtfs.getIndexedCalendars())).to.deep.equal(['service_0', 'service_1', 'service_3']); 27 | 28 | gtfs.removeCalendars([gtfs.getCalendarWithServiceId('service_0'), gtfs.getCalendarWithServiceId('service_3')]); 29 | expect(sortedKeys(gtfs.getIndexedCalendars())).to.deep.equal(['service_1']); 30 | 31 | gtfs.setIndexedCalendars(new Map([['service_0', calendar0]])); 32 | expect(sortedKeys(gtfs.getIndexedCalendars())).to.deep.equal(['service_0']); 33 | 34 | const serviceIds = []; 35 | gtfs.forEachCalendar((calendar) => { 36 | serviceIds.push(calendar.service_id); 37 | }); 38 | expect(serviceIds).to.deep.equal(['service_0']); 39 | 40 | const trip0 = gtfs.getTripWithId('trip_0'); 41 | const calendarOfTrip0 = gtfs.getCalendarOfTrip(trip0); 42 | expect(calendarOfTrip0.start_date).to.equal('20000101'); 43 | 44 | const stopTime00 = gtfs.getStopTimeWithTripIdAndStopSequence('trip_0', '0'); 45 | const calendarOfStopTime00 = gtfs.getCalendarOfStopTime(stopTime00); 46 | expect(calendarOfStopTime00.start_date).to.equal('20000101'); 47 | 48 | done(); 49 | }); 50 | 51 | it('Tests on gtfs.resetCalendars()', (done) => { 52 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 53 | 54 | expect(gtfs.getIndexedCalendars().size).to.equal(1); 55 | 56 | gtfs.resetCalendars(); 57 | 58 | expect(gtfs.getIndexedCalendars().size).to.equal(0); 59 | 60 | done(); 61 | }); 62 | }); 63 | 64 | function sortedKeys(map) { 65 | return Array.from(map.keys()).sort(); 66 | } 67 | -------------------------------------------------------------------------------- /tests/test_trips.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { expect } = require('chai'); 4 | const { Gtfs } = require('../index'); 5 | 6 | describe('Tests on GTFS trips', () => { 7 | it('Tests on trips functions', (done) => { 8 | const path = `${__dirname}/samples/1`; 9 | const gtfs = new Gtfs(path); 10 | 11 | expect(sortedKeys(gtfs.getIndexedTrips())).to.deep.equal(['trip_0']); 12 | 13 | const trip0 = gtfs.getTripWithId('trip_0'); 14 | expect(trip0.trip_headsign).to.equal('Trip 0'); 15 | 16 | gtfs.addTrip({ trip_id: 'trip_1', trip_headsign: 'Trip 1' }); 17 | expect(sortedKeys(gtfs.getIndexedTrips())).to.deep.equal(['trip_0', 'trip_1']); 18 | 19 | gtfs.addTrips([ 20 | { trip_id: 'trip_2', trip_headsign: 'Trip 2' }, 21 | { trip_id: 'trip_3', trip_headsign: 'Trip 3' }, 22 | ]); 23 | expect(sortedKeys(gtfs.getIndexedTrips())).to.deep.equal(['trip_0', 'trip_1', 'trip_2', 'trip_3']); 24 | 25 | gtfs.removeTrip(gtfs.getTripWithId('trip_2')); 26 | expect(sortedKeys(gtfs.getIndexedTrips())).to.deep.equal(['trip_0', 'trip_1', 'trip_3']); 27 | 28 | gtfs.removeTrips([gtfs.getTripWithId('trip_0'), gtfs.getTripWithId('trip_3')]); 29 | expect(sortedKeys(gtfs.getIndexedTrips())).to.deep.equal(['trip_1']); 30 | 31 | gtfs.setIndexedTrips(new Map([['trip_0', trip0]])); 32 | expect(sortedKeys(gtfs.getIndexedTrips())).to.deep.equal(['trip_0']); 33 | 34 | const tripIds = []; 35 | gtfs.forEachTrip((trip) => { 36 | tripIds.push(trip.trip_id); 37 | }); 38 | expect(tripIds).to.deep.equal(['trip_0']); 39 | 40 | const stopTime00 = gtfs.getStopTimeWithTripIdAndStopSequence('trip_0', '0'); 41 | const tripOfStopTime00 = gtfs.getTripOfStopTime(stopTime00); 42 | expect(tripOfStopTime00.trip_headsign).to.equal('Trip 0'); 43 | 44 | done(); 45 | }); 46 | 47 | it('Tests on gtfs.getNumberOfTrips()', (done) => { 48 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 49 | 50 | expect(gtfs.getNumberOfTrips()).to.equal(1); 51 | 52 | gtfs.resetTrips(); 53 | 54 | expect(gtfs.getNumberOfTrips()).to.equal(0); 55 | 56 | done(); 57 | }); 58 | 59 | it('Tests on gtfs.getSampleTrip()', (done) => { 60 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 61 | 62 | const trip = gtfs.getSampleTrip(); 63 | 64 | expect(trip.trip_id).to.equal('trip_0'); 65 | expect(trip.trip_headsign).to.equal('Trip 0'); 66 | 67 | done(); 68 | }); 69 | 70 | it('Tests on gtfs.resetTrips()', (done) => { 71 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 72 | 73 | expect(gtfs.getIndexedTrips().size).to.equal(1); 74 | 75 | gtfs.resetTrips(); 76 | 77 | expect(gtfs.getIndexedTrips().size).to.equal(0); 78 | 79 | done(); 80 | }); 81 | }); 82 | 83 | function sortedKeys(map) { 84 | return Array.from(map.keys()).sort(); 85 | } 86 | -------------------------------------------------------------------------------- /tests/test_transfers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { expect } = require('chai'); 4 | const { Gtfs } = require('../index'); 5 | 6 | describe('Tests on GTFS transfers', () => { 7 | it('Tests on transfers functions', (done) => { 8 | const path = `${__dirname}/samples/1`; 9 | const gtfs = new Gtfs(path); 10 | 11 | expect(sortedKeys(gtfs.getIndexedTransfers())).to.deep.equal(['stop_0', 'stop_1']); 12 | 13 | const transfer01 = gtfs.getTransferWithFromStopIdAndToStopId('stop_0', 'stop_1'); 14 | expect(transfer01.transfer_type).to.equal('0'); 15 | 16 | gtfs.addTransfer({ from_stop_id: 'stop_2', to_stop_id: 'stop_0', transfer_type: '3' }); 17 | expect(sortedKeys(gtfs.getIndexedTransfers())).to.deep.equal(['stop_0', 'stop_1', 'stop_2']); 18 | 19 | gtfs.addTransfers([ 20 | { from_stop_id: 'stop_3', to_stop_id: 'stop_0', transfer_type: '3' }, 21 | { from_stop_id: 'stop_4', to_stop_id: 'stop_0', transfer_type: '3' }, 22 | ]); 23 | expect(sortedKeys(gtfs.getIndexedTransfers())).to.deep.equal(['stop_0', 'stop_1', 'stop_2', 'stop_3', 'stop_4']); 24 | 25 | gtfs.removeTransfer(gtfs.getTransferWithFromStopIdAndToStopId('stop_0', 'stop_1')); 26 | expect(sortedKeys(gtfs.getIndexedTransfers())).to.deep.equal(['stop_1', 'stop_2', 'stop_3', 'stop_4']); 27 | 28 | gtfs.removeTransfers([ 29 | gtfs.getTransferWithFromStopIdAndToStopId('stop_1', 'stop_0'), 30 | gtfs.getTransferWithFromStopIdAndToStopId('stop_3', 'stop_0'), 31 | ]); 32 | expect(sortedKeys(gtfs.getIndexedTransfers())).to.deep.equal(['stop_2', 'stop_4']); 33 | 34 | gtfs.setIndexedTransfers(new Map([ 35 | ['stop_0', new Map([ 36 | ['stop_1', { from_stop_id: 'stop_0', to_stop_id: 'stop_1', transfer_type: '0' }], 37 | ['stop_2', { from_stop_id: 'stop_0', to_stop_id: 'stop_2', transfer_type: '3' }], 38 | ])], 39 | ['stop_1', new Map([ 40 | ['stop_0', { from_stop_id: 'stop_1', to_stop_id: 'stop_0', transfer_type: '1' }], 41 | ['stop_3', { from_stop_id: 'stop_1', to_stop_id: 'stop_3', transfer_type: '3' }], 42 | ])], 43 | ])); 44 | expect(sortedKeys(gtfs.getIndexedTransfers())).to.deep.equal(['stop_0', 'stop_1']); 45 | const transfer02 = gtfs.getTransferWithFromStopIdAndToStopId('stop_0', 'stop_2'); 46 | expect(transfer02.transfer_type).to.equal('3'); 47 | const transfer10 = gtfs.getTransferWithFromStopIdAndToStopId('stop_1', 'stop_0'); 48 | expect(transfer10.transfer_type).to.equal('1'); 49 | 50 | const transferTypes = []; 51 | gtfs.forEachTransfer((transfer) => { 52 | transferTypes.push(transfer.transfer_type); 53 | }); 54 | expect(transferTypes.sort()).to.deep.equal(['0', '1', '3', '3']); 55 | 56 | done(); 57 | }); 58 | 59 | it('Tests on gtfs.resetTransfers()', (done) => { 60 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 61 | 62 | expect(gtfs.getIndexedTransfers().size).to.equal(2); 63 | 64 | gtfs.resetTransfers(); 65 | 66 | expect(gtfs.getIndexedTransfers().size).to.equal(0); 67 | 68 | done(); 69 | }); 70 | }); 71 | 72 | function sortedKeys(map) { 73 | return Array.from(map.keys()).sort(); 74 | } 75 | -------------------------------------------------------------------------------- /tests/test_routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { expect } = require('chai'); 4 | const { Gtfs } = require('../index'); 5 | 6 | describe('Tests on GTFS routes', () => { 7 | it('Tests on routes functions', (done) => { 8 | const path = `${__dirname}/samples/1`; 9 | const gtfs = new Gtfs(path); 10 | 11 | expect(sortedKeys(gtfs.getIndexedRoutes())).to.deep.equal(['route_0', 'route_utf8', 'route_x', 'route_y']); 12 | 13 | const route0 = gtfs.getRouteWithId('route_0'); 14 | expect(route0.route_long_name).to.equal('Route 0'); 15 | 16 | const routeX = gtfs.getRouteWithId('route_x'); 17 | expect(routeX.route_long_name).to.equal('"Route X"'); 18 | 19 | gtfs.addRoute({ route_id: 'route_1', route_long_name: 'Route 1' }); 20 | expect(sortedKeys(gtfs.getIndexedRoutes())).to.deep.equal(['route_0', 'route_1', 'route_utf8', 'route_x', 'route_y']); 21 | 22 | gtfs.addRoutes([ 23 | { route_id: 'route_2', route_long_name: 'Route 2' }, 24 | { route_id: 'route_3', route_long_name: 'Route 3' }, 25 | ]); 26 | expect(sortedKeys(gtfs.getIndexedRoutes())).to.deep.equal(['route_0', 'route_1', 'route_2', 27 | 'route_3', 'route_utf8', 'route_x', 'route_y']); 28 | 29 | gtfs.removeRoute(gtfs.getRouteWithId('route_2')); 30 | expect(sortedKeys(gtfs.getIndexedRoutes())).to.deep.equal(['route_0', 'route_1', 'route_3', 31 | 'route_utf8', 'route_x', 'route_y']); 32 | 33 | gtfs.removeRoutes([gtfs.getRouteWithId('route_0'), gtfs.getRouteWithId('route_3')]); 34 | expect(sortedKeys(gtfs.getIndexedRoutes())).to.deep.equal(['route_1', 'route_utf8', 'route_x', 'route_y']); 35 | 36 | gtfs.setIndexedRoutes(new Map([['route_0', route0]])); 37 | expect(sortedKeys(gtfs.getIndexedRoutes())).to.deep.equal(['route_0']); 38 | 39 | const routeIds = []; 40 | gtfs.forEachRoute((route) => { 41 | routeIds.push(route.route_id); 42 | }); 43 | expect(routeIds).to.deep.equal(['route_0']); 44 | 45 | const trip0 = gtfs.getTripWithId('trip_0'); 46 | const routeOfTrip0 = gtfs.getRouteOfTrip(trip0); 47 | expect(routeOfTrip0.route_long_name).to.equal('Route 0'); 48 | 49 | const stopTime00 = gtfs.getStopTimeWithTripIdAndStopSequence('trip_0', '0'); 50 | const routeOfStopTime00 = gtfs.getRouteOfStopTime(stopTime00); 51 | expect(routeOfStopTime00.route_long_name).to.equal('Route 0'); 52 | 53 | done(); 54 | }); 55 | 56 | it('Tests on gtfs.getNumberOfRoutes()', (done) => { 57 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 58 | 59 | expect(gtfs.getNumberOfRoutes()).to.equal(4); 60 | 61 | gtfs.resetRoutes(); 62 | 63 | expect(gtfs.getNumberOfRoutes()).to.equal(0); 64 | 65 | done(); 66 | }); 67 | 68 | it('Tests on gtfs.getSampleRoute()', (done) => { 69 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 70 | 71 | const route = gtfs.getSampleRoute(); 72 | 73 | expect(route.route_id).to.equal('route_0'); 74 | expect(route.route_short_name).to.equal('R0'); 75 | 76 | done(); 77 | }); 78 | 79 | it('Tests on gtfs.resetRoutes()', (done) => { 80 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 81 | 82 | expect(gtfs.getIndexedRoutes().size).to.equal(4); 83 | 84 | gtfs.resetRoutes(); 85 | 86 | expect(gtfs.getIndexedRoutes().size).to.equal(0); 87 | 88 | done(); 89 | }); 90 | }); 91 | 92 | function sortedKeys(map) { 93 | return Array.from(map.keys()).sort(); 94 | } 95 | -------------------------------------------------------------------------------- /tests/test_pathways.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { expect } = require('chai'); 4 | const { Gtfs } = require('../index'); 5 | 6 | describe('Tests on GTFS pathways', () => { 7 | it('Tests on pathways functions', (done) => { 8 | const path = `${__dirname}/samples/1`; 9 | const gtfs = new Gtfs(path); 10 | expect(sortedKeys(gtfs.getIndexedPathways())).to.deep.equal(['1', '2']); 11 | 12 | const pathway1 = gtfs.getPathwayWithPathwayId('1'); 13 | const pathway2 = gtfs.getPathwayWithPathwayId('2'); 14 | expect(pathway1.pathway_mode).to.equal('1'); 15 | expect(pathway1.is_bidirectional).to.equal('0'); 16 | 17 | gtfs.addPathway({ 18 | pathway_id: '3', 19 | from_stop_id: 'stop_4', 20 | to_stop_id: 'stop_5', 21 | pathway_mode: '1', 22 | is_bidirectional: '0', 23 | length: '100', 24 | traversal_time: '60', 25 | stair_count: '12', 26 | max_slope: '', 27 | min_width: '', 28 | signposted_as: 'signposted_as', 29 | reversed_signposted_as: 'reversed_signposted_as', 30 | }); 31 | expect(sortedKeys(gtfs.getIndexedPathways())).to.deep.equal(['1', '2', '3']); 32 | 33 | gtfs.addPathways([ 34 | { 35 | pathway_id: '4', 36 | from_stop_id: 'stop_4', 37 | to_stop_id: 'stop_6', 38 | pathway_mode: '1', 39 | is_bidirectional: '0', 40 | length: '100', 41 | traversal_time: '60', 42 | stair_count: '12', 43 | max_slope: '', 44 | min_width: '', 45 | signposted_as: 'signposted_as', 46 | reversed_signposted_as: 'reversed_signposted_as', 47 | }, 48 | { 49 | pathway_id: '5', 50 | from_stop_id: 'stop_6', 51 | to_stop_id: 'stop_7', 52 | pathway_mode: '1', 53 | is_bidirectional: '0', 54 | length: '100', 55 | traversal_time: '60', 56 | stair_count: '12', 57 | max_slope: '', 58 | min_width: '', 59 | signposted_as: 'signposted_as', 60 | reversed_signposted_as: 'reversed_signposted_as', 61 | }, 62 | ]); 63 | expect(sortedKeys(gtfs.getIndexedPathways())).to.deep.equal(['1', '2', '3', '4', '5']); 64 | 65 | gtfs.removePathway(gtfs.getPathwayWithPathwayId('1')); 66 | expect(sortedKeys(gtfs.getIndexedPathways())).to.deep.equal(['2', '3', '4', '5']); 67 | 68 | gtfs.removePathways([ 69 | gtfs.getPathwayWithPathwayId('2'), 70 | gtfs.getPathwayWithPathwayId('3'), 71 | ]); 72 | 73 | expect(sortedKeys(gtfs.getIndexedPathways())).to.deep.equal(['4', '5']); 74 | 75 | gtfs.setIndexedPathways(new Map([['1', pathway1], ['2', pathway2]])); 76 | 77 | expect(sortedKeys(gtfs.getIndexedPathways())).to.deep.equal(['1', '2']); 78 | 79 | const pathwayModes = []; 80 | gtfs.forEachPathway((pathway) => { 81 | pathwayModes.push(pathway.pathway_mode); 82 | }); 83 | expect(pathwayModes.sort()).to.deep.equal(['1', '2']); 84 | 85 | done(); 86 | }); 87 | 88 | it('Tests on gtfs.resetTransfers()', (done) => { 89 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 90 | 91 | expect(gtfs.getIndexedPathways().size).to.equal(2); 92 | 93 | gtfs.resetPathways(); 94 | 95 | expect(gtfs.getIndexedPathways().size).to.equal(0); 96 | 97 | done(); 98 | }); 99 | }); 100 | 101 | function sortedKeys(map) { 102 | return Array.from(map.keys()).sort(); 103 | } 104 | -------------------------------------------------------------------------------- /tests/test_frequencies.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { expect } = require('chai'); 4 | const { Gtfs } = require('../index'); 5 | 6 | describe('Tests on GTFS frequencies', () => { 7 | it('Tests on frequencies functions', (done) => { 8 | const path = `${__dirname}/samples/1`; 9 | const gtfs = new Gtfs(path); 10 | 11 | expect(sortedKeys(gtfs.getIndexedFrequencies())).to.deep.equal(['trip_0']); 12 | 13 | const frequency010h = gtfs.getFrequencyWithTripIdAndStartTime('trip_0', '10:00:00'); 14 | expect(frequency010h.headway_secs).to.equal('600'); 15 | 16 | gtfs.addFrequency({ trip_id: 'trip_1', start_time: '20:00:00', end_time: '25:00:00' }); 17 | expect(sortedKeys(gtfs.getIndexedFrequencies())).to.deep.equal(['trip_0', 'trip_1']); 18 | 19 | gtfs.addFrequencies([ 20 | { trip_id: 'trip_2', start_time: '20:00:00', end_time: '25:00:00' }, 21 | { trip_id: 'trip_3', start_time: '20:00:00', end_time: '25:00:00' }, 22 | ]); 23 | expect(sortedKeys(gtfs.getIndexedFrequencies())).to.deep.equal(['trip_0', 'trip_1', 'trip_2', 'trip_3']); 24 | 25 | gtfs.removeFrequency(gtfs.getFrequencyWithTripIdAndStartTime('trip_2', '20:00:00')); 26 | expect(sortedKeys(gtfs.getIndexedFrequencies())).to.deep.equal(['trip_0', 'trip_1', 'trip_3']); 27 | 28 | gtfs.removeFrequencies([ 29 | gtfs.getFrequencyWithTripIdAndStartTime('trip_0', '10:00:00'), 30 | gtfs.getFrequencyWithTripIdAndStartTime('trip_0', '15:00:00'), 31 | ]); 32 | expect(sortedKeys(gtfs.getIndexedFrequencies())).to.deep.equal(['trip_1', 'trip_3']); 33 | 34 | gtfs.setIndexedFrequencies(new Map([ 35 | ['trip_0', new Map([ 36 | ['05:00:00', { trip_id: 'trip_0', start_time: '05:00:00', end_time: '10:00:00' }], 37 | ['10:00:00', { trip_id: 'trip_0', start_time: '10:00:00', end_time: '15:00:00' }], 38 | ])], 39 | ['trip_1', new Map([ 40 | ['05:00:00', { trip_id: 'trip_1', start_time: '05:00:00', end_time: '10:00:00' }], 41 | ['10:00:00', { trip_id: 'trip_1', start_time: '10:00:00', end_time: '16:00:00' }], 42 | ])], 43 | ])); 44 | expect(sortedKeys(gtfs.getIndexedFrequencies())).to.deep.equal(['trip_0', 'trip_1']); 45 | const frequency110h = gtfs.getFrequencyWithTripIdAndStartTime('trip_1', '10:00:00'); 46 | expect(frequency110h.end_time).to.equal('16:00:00'); 47 | 48 | const endTimes = []; 49 | gtfs.forEachFrequency((frequency) => { 50 | endTimes.push(frequency.end_time); 51 | }); 52 | expect(endTimes.sort()).to.deep.equal(['10:00:00', '10:00:00', '15:00:00', '16:00:00']); 53 | 54 | done(); 55 | }); 56 | 57 | it('Tests on gtfs.resetFrequencies()', (done) => { 58 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 59 | 60 | gtfs.setIndexedFrequencies(new Map([ 61 | ['trip_0', new Map([ 62 | ['05:00:00', { trip_id: 'trip_0', start_time: '05:00:00', end_time: '10:00:00' }], 63 | ['10:00:00', { trip_id: 'trip_0', start_time: '10:00:00', end_time: '15:00:00' }], 64 | ])], 65 | ['trip_1', new Map([ 66 | ['05:00:00', { trip_id: 'trip_1', start_time: '05:00:00', end_time: '10:00:00' }], 67 | ['10:00:00', { trip_id: 'trip_1', start_time: '10:00:00', end_time: '16:00:00' }], 68 | ])], 69 | ])); 70 | 71 | expect(gtfs.getIndexedFrequencies().size).to.equal(2); 72 | 73 | gtfs.resetFrequencies(); 74 | 75 | expect(gtfs.getIndexedFrequencies().size).to.equal(0); 76 | 77 | done(); 78 | }); 79 | }); 80 | 81 | function sortedKeys(map) { 82 | return Array.from(map.keys()).sort(); 83 | } 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node.js light GTFS loading and manipulation 2 | A Node.js naive library to load and manipulate GTFS datasets. 3 | 4 | ## Installation 5 | 6 | ```npm install --save @transit/gtfs``` 7 | 8 | ## Usage 9 | 10 | The tables of the GTFS will be loaded only when accessed, and not upfront. This allows manipulation of the small tables 11 | (like routes.txt or stops.txt) without having to load the big tables (like stop_times.txt). 12 | 13 | ## Example 14 | 15 | If you want to remove all the stops called 'Central Station' and the stop_times using this stop: 16 | 17 | ```js 18 | const { Gtfs } = require('@transit/gtfs'); 19 | 20 | const gtfs = new Gtfs('pathToTheFolderContainingTheGtfs'); 21 | 22 | gtfs.forEachStop((stop) => { 23 | if (stop.stop_name === 'Central Station') { 24 | gtfs.removeStop(stop); 25 | } 26 | }); 27 | 28 | gtfs.forEachStopTime((stopTime) => { 29 | if (!gtfs.getStopOfStopTime(stopTime)) { 30 | gtfs.removeStopTime(stopTime); 31 | } 32 | }); 33 | 34 | // Let's also clean up the frequencies, to keep a consistent GTFS. 35 | gtfs.forEachFrequency((frequency) => { 36 | const fromStop = gtfs.getStopWithId(frequency.from_stop_id); 37 | const toStop = gtfs.getStopWithId(frequency.to_stop_id); 38 | 39 | if (!fromStop || !toStop) { 40 | gtfs.removeFrequency(frequency); 41 | } 42 | }); 43 | 44 | gtfs.exportAtPath('somePathWhereYouWantToExportTheGtfs', (error) => { 45 | if (error) { throw error }; 46 | 47 | // Done 48 | }); 49 | ``` 50 | 51 | ### Indexes 52 | 53 | The tables are loaded and saved as Maps, to allow o(1) access using the ids. The routes are therefore indexed by the 54 | `route_id` value, which is therefore saved in `route.route_id` but also as an index. 55 | 56 | **This indexing is not automatically kept up to date.** 57 | 58 | If you change the `route_id` just by changing the internal value of the `route` the index **won't** be updated, and 59 | therefore the table will be corrupted. To properly update the id of a route, you should replace it: 60 | 61 | ```js 62 | const route = gtfs.getRouteWithId('oldId'); 63 | gtfs.removeRoute(route); 64 | route.route_id = 'newId'; 65 | gtfs.addRoute(route); 66 | ``` 67 | 68 | ### Synchronous loading 69 | 70 | The goal of this implementation was to avoid loading upfront all the tables. Therefore, they are loaded only when 71 | required. This makes the code faster to run (if some tables are not required at all). 72 | 73 | The drawback, is that any function could trigger the loading of a table. Since we do not want to turn every function into an async one, the loading of the tables is done synchronously. 74 | 75 | ## Naming 76 | 77 | The wording used in the official GTFS specification has been followed as much as possible, including the inconsistencies. 78 | For example, the table containing the stops is "stops", but the table containing the agencies is "agency". The reason 79 | for this being that, in the specification, the files are named `stops.txt` vs `agency.txt`. 80 | 81 | Most of the time, the name of one item of a table is the singular of the table name (routes -> route, stops -> stop), 82 | but for the `shapes.txt`, since one item of the table is not a "shape" per-se, but just a point, the name used is 83 | "shapePoint" (consistent with the name `shape_pt_sequence`, `shape_pt_lat` and `shape_pt_lon` of the spec). 84 | 85 | ## Support and contact 86 | 87 | Please post any issues you find on [the repo of the project](https://github.com/TransitApp/gtfsNodeLib/issues). And 88 | do not hesitate to contact [Transit App](https://github.com/TransitApp) directly if you have any questions. 89 | 90 | 91 | -------------------------------------------------------------------------------- /tests/test_generic_table_functions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { expect } = require('chai'); 4 | const { Gtfs } = require('../index'); 5 | 6 | describe('Tests on GTFS generic table functions', () => { 7 | it('Test on generic table functions', (done) => { 8 | const path = `${__dirname}/samples/1/`; 9 | const gtfs = new Gtfs(path); 10 | 11 | const indexedAgencies = gtfs.getIndexedTable('agency'); 12 | expect(indexedAgencies.get('agency_0').agency_name).to.equal('Agency 0'); 13 | 14 | const agency0 = gtfs.getItemWithIndexInTable('agency_0', 'agency'); 15 | expect(agency0.agency_name).to.equal('Agency 0'); 16 | 17 | const expectedTableNames = [ 18 | 'agency', 'calendar', 'calendar_dates', 'fare_attributes', 'fare_rules', 'frequencies', 19 | 'routes', 'stop_times', 'stops', 'trips', 'shapes', 'transfers', 'pathways', 'feed_info', 20 | ]; 21 | expect(Array.from(gtfs.getTableNames())).to.deep.equal(expectedTableNames); 22 | 23 | const tableNames = []; 24 | gtfs.forEachTableName((tableName) => { 25 | tableNames.push(tableName); 26 | }); 27 | expect(tableNames).to.deep.equal(expectedTableNames); 28 | 29 | const ROUTE_TABLE_NAME = 'routes'; 30 | const route0 = gtfs.getRouteWithId('route_0'); 31 | const uftRouteId = 32 | 33 | gtfs.addItemInTable({ route_id: 'route_1', route_long_name: 'Route 1' }, ROUTE_TABLE_NAME); 34 | expect(sortedKeys(gtfs.getIndexedRoutes())).to.deep.equal(['route_0', 'route_1', 'route_utf8', 'route_x', 'route_y']); 35 | 36 | gtfs.addItemsInTable([ 37 | { route_id: 'route_2', route_long_name: 'Route 2' }, 38 | { route_id: 'route_3', route_long_name: 'Route 3' }, 39 | ], ROUTE_TABLE_NAME); 40 | expect(sortedKeys(gtfs.getIndexedRoutes())).to.deep.equal(['route_0', 'route_1', 'route_2', 41 | 'route_3', 'route_utf8', 'route_x', 'route_y']); 42 | 43 | gtfs.removeItemInTable(gtfs.getRouteWithId('route_2'), ROUTE_TABLE_NAME); 44 | expect(sortedKeys(gtfs.getIndexedRoutes())).to.deep.equal(['route_0', 'route_1', 'route_3', 45 | 'route_utf8', 'route_x', 'route_y']); 46 | 47 | gtfs.removeItemsInTable([gtfs.getRouteWithId('route_0'), gtfs.getRouteWithId('route_3')], ROUTE_TABLE_NAME); 48 | expect(sortedKeys(gtfs.getIndexedRoutes())).to.deep.equal(['route_1', 'route_utf8', 'route_x', 'route_y']); 49 | 50 | gtfs.setIndexedItemsAsTable(new Map([['route_0', route0]]), ROUTE_TABLE_NAME); 51 | expect(sortedKeys(gtfs.getIndexedRoutes())).to.deep.equal(['route_0']); 52 | 53 | const routeIds = []; 54 | gtfs.forEachItemInTable(ROUTE_TABLE_NAME, (route) => { 55 | routeIds.push(route.route_id); 56 | }); 57 | expect(routeIds).to.deep.equal(['route_0']); 58 | 59 | const trip0 = gtfs.getTripWithId('trip_0'); 60 | const routeOfTrip0 = gtfs.getParentItem(trip0, ROUTE_TABLE_NAME); 61 | expect(routeOfTrip0.route_long_name).to.equal('Route 0'); 62 | 63 | done(); 64 | }); 65 | 66 | it('Test on gtfs.resetTable(tableName)', (done) => { 67 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 68 | 69 | expect(gtfs.getIndexedTransfers().size).to.equal(2); 70 | 71 | gtfs.resetTable('transfers'); 72 | 73 | expect(gtfs.getIndexedTransfers().size).to.equal(0); 74 | 75 | done(); 76 | }); 77 | 78 | it('Test on gtfs.getNumberOfItemsInTable(tableName)', (done) => { 79 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 80 | 81 | expect(gtfs.getNumberOfItemsInTable('routes')).to.equal(4); 82 | 83 | gtfs.resetRoutes(); 84 | expect(gtfs.getNumberOfItemsInTable('routes')).to.equal(0); 85 | 86 | expect(() => gtfs.getNumberOfItemsInTable('stop_times')).to.throw(); 87 | 88 | done(); 89 | }); 90 | }); 91 | 92 | function sortedKeys(map) { 93 | return Array.from(map.keys()).sort(); 94 | } 95 | -------------------------------------------------------------------------------- /tests/test_fare_rules.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { expect } = require('chai'); 4 | const { Gtfs } = require('../index'); 5 | 6 | describe('Tests on GTFS fare rules', () => { 7 | it('Tests on gtfs.getNumberOfFareRules(), .addFareRules?(), .getFareRules?() and .hasFareRule', (done) => { 8 | const path = `${__dirname}/samples/1`; 9 | const gtfs = new Gtfs(path); 10 | const fareRule1 = buildFareRule(1, gtfs); 11 | const fareRule2 = buildFareRule(2, gtfs); 12 | const fareRule3 = buildFareRule(3, gtfs); 13 | 14 | expect(gtfs.getNumberOfFareRules()).to.equal(0); 15 | expect(gtfs.hasFareRule(fareRule1)).to.equal(false); 16 | expect(gtfs.hasFareRule(fareRule2)).to.equal(false); 17 | expect(gtfs.hasFareRule(fareRule3)).to.equal(false); 18 | 19 | gtfs.addFareRules([fareRule1, fareRule2]); 20 | 21 | expect(gtfs.getNumberOfFareRules()).to.equal(2); 22 | expect(gtfs.hasFareRule(fareRule1)).to.equal(true); 23 | expect(gtfs.hasFareRule(fareRule2)).to.equal(true); 24 | expect(gtfs.hasFareRule(fareRule3)).to.equal(false); 25 | 26 | gtfs.addFareRule(fareRule3); 27 | 28 | expect(gtfs.getNumberOfFareRules()).to.equal(3); 29 | expect(gtfs.hasFareRule(fareRule1)).to.equal(true); 30 | expect(gtfs.hasFareRule(fareRule2)).to.equal(true); 31 | expect(gtfs.hasFareRule(fareRule3)).to.equal(true); 32 | 33 | gtfs.removeFareRule(fareRule2); 34 | 35 | expect(gtfs.getNumberOfFareRules()).to.equal(2); 36 | expect(gtfs.hasFareRule(fareRule1)).to.equal(true); 37 | expect(gtfs.hasFareRule(fareRule2)).to.equal(false); 38 | expect(gtfs.hasFareRule(fareRule3)).to.equal(true); 39 | 40 | gtfs.removeFareRules([fareRule1, fareRule3]); 41 | 42 | expect(gtfs.getNumberOfFareRules()).to.equal(0); 43 | expect(gtfs.hasFareRule(fareRule1)).to.equal(false); 44 | expect(gtfs.hasFareRule(fareRule2)).to.equal(false); 45 | expect(gtfs.hasFareRule(fareRule3)).to.equal(false); 46 | 47 | done(); 48 | }); 49 | 50 | it('Tests on gtfs.setFareRules() and .resetFareRules', (done) => { 51 | const path = `${__dirname}/samples/1`; 52 | const gtfs = new Gtfs(path); 53 | const fareRule1 = buildFareRule(1, gtfs); 54 | const fareRule2 = buildFareRule(2, gtfs); 55 | const fareRule3 = buildFareRule(3, gtfs); 56 | 57 | expect(gtfs.getNumberOfFareRules()).to.equal(0); 58 | expect(gtfs.hasFareRule(fareRule1)).to.equal(false); 59 | expect(gtfs.hasFareRule(fareRule2)).to.equal(false); 60 | expect(gtfs.hasFareRule(fareRule3)).to.equal(false); 61 | 62 | gtfs.setFareRules(new Set([fareRule1, fareRule2])); 63 | 64 | expect(gtfs.getNumberOfFareRules()).to.equal(2); 65 | expect(gtfs.hasFareRule(fareRule1)).to.equal(true); 66 | expect(gtfs.hasFareRule(fareRule2)).to.equal(true); 67 | expect(gtfs.hasFareRule(fareRule3)).to.equal(false); 68 | 69 | gtfs.resetFareRules(); 70 | 71 | expect(gtfs.getNumberOfFareRules()).to.equal(0); 72 | expect(gtfs.hasFareRule(fareRule1)).to.equal(false); 73 | expect(gtfs.hasFareRule(fareRule2)).to.equal(false); 74 | expect(gtfs.hasFareRule(fareRule3)).to.equal(false); 75 | 76 | done(); 77 | }); 78 | 79 | it('Tests on gtfs.forEachFareRule()', (done) => { 80 | const path = `${__dirname}/samples/1`; 81 | const gtfs = new Gtfs(path); 82 | gtfs.addFareRules([buildFareRule(1, gtfs), buildFareRule(3, gtfs)]); 83 | 84 | const fareIds = []; 85 | gtfs.forEachFareRule(fareRule => fareIds.push(fareRule.fare_id)); 86 | 87 | expect(fareIds.sort()).to.deep.equal(['fareRule.fare_id.1', 'fareRule.fare_id.3']); 88 | 89 | done(); 90 | }); 91 | }); 92 | 93 | function buildFareRule(integer, gtfs) { 94 | return gtfs.createGtfsObjectFromSimpleObject({ 95 | 'fare_id': `fareRule.fare_id.${integer}`, 96 | 'route_id': `fareRule.route_id.${integer}`, 97 | 'origin_id': `fareRule.origin_id.${integer}`, 98 | 'destination_id': `fareRule.destination_id.${integer}`, 99 | 'contains_id': `fareRule.contains_id.${integer}`, 100 | }); 101 | } 102 | -------------------------------------------------------------------------------- /tests/test_gtfs_options.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Run the tests with mocha: mocha tests.js */ 4 | 5 | // eslint-disable-next-line import/no-extraneous-dependencies 6 | const { expect } = require('chai'); 7 | const fs = require('fs-extra'); 8 | 9 | const { Gtfs } = require('../index'); 10 | 11 | describe('Tests on GTFS constructor options', () => { 12 | it('Test on forcedSchema', (done) => { 13 | const forcedSchema = Gtfs.getDefaultSchema(); 14 | forcedSchema.deepnessByTableName.modes = 1; 15 | forcedSchema.indexKeysByTableName.modes = { indexKey: 'mode_id' }; 16 | forcedSchema.keysByTableName.modes = [ 17 | 'mode_id', 18 | 'mode_name', 19 | 'mode_url', 20 | 'mode_timezone', 21 | 'mode_lang', 22 | 'mode_phone', 23 | 'mode_fare_url', 24 | 'mode_email', 25 | ]; 26 | forcedSchema.tableNames.push('modes'); 27 | 28 | const path = `${__dirname}/samples/1/`; 29 | const gtfs = new Gtfs(path, { forcedSchema }); 30 | 31 | const mode = gtfs.getItemWithIndexInTable('mode_0', 'modes'); 32 | 33 | expect(mode.mode_name).to.equal('mode 0'); 34 | 35 | done(); 36 | }); 37 | 38 | it('Test on postImportTableFunction', (done) => { 39 | const path = `${__dirname}/samples/1/`; 40 | const postImportItemFunction = (item) => { item.temp = 'some value'; }; 41 | const gtfs = new Gtfs(path, { postImportItemFunction }); 42 | 43 | const route = gtfs.getRouteWithId('route_0'); 44 | 45 | expect(route.temp).to.equal('some value'); 46 | 47 | const outputPath = `${__dirname}/temp_4865de67d01696s48dfbd0e71adx8f0b/`; 48 | gtfs.exportAtPath(outputPath, (exportError) => { 49 | if (exportError) { throw exportError; } 50 | 51 | fs.readFile(`${outputPath}routes.txt`, (readRoutesError, routesTxt) => { 52 | if (readRoutesError) { throw readRoutesError; } 53 | 54 | expect(String(routesTxt)).to.equal( 55 | 'route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,' + 56 | 'route_text_color,route_sort_order,tts_route_short_name,tts_route_long_name,temp\r\n' + 57 | 'route_0,agency_0,R0,Route 0,,3,,,,,r0,rooouuuteee 0,some value\r\n' + 58 | 'route_x,agency_0,RX,"""Route X""",,3,,,,,,,some value\r\n' + 59 | 'route_utf8,agency_0,RÛTF8,route_😎êωn → ∞⠁⠧⠑ ⠼éöÿΚαλημέρα\'´`,,3,,,,,旱獺屬,,some value\r\n' + 60 | 'route_y,agency_0,RY,"{""routeLongName"":""""}",,3,,,,,,,some value' 61 | ); 62 | 63 | fs.remove(outputPath, (removeError) => { 64 | if (removeError) { throw removeError; } 65 | 66 | done(); 67 | }); 68 | }); 69 | }); 70 | }); 71 | 72 | it('Test on postImportTableFunction', (done) => { 73 | const path = `${__dirname}/samples/1/`; 74 | const postImportItemFunction = (item) => { item.temp = { key: 'value' }; }; 75 | const preExportItemFunction = (item) => { 76 | const item2 = item.clone(); 77 | item2.temp = JSON.stringify(item.temp); 78 | return item2; 79 | }; 80 | const gtfs = new Gtfs(path, { postImportItemFunction, preExportItemFunction }); 81 | 82 | const route = gtfs.getRouteWithId('route_0'); 83 | 84 | expect(route.temp).to.deep.equal({ key: 'value' }); 85 | 86 | const outputPath = `${__dirname}/temp_4865de67d01f96s489fbd0e71ad88f0b/`; 87 | gtfs.exportAtPath(outputPath, (exportError) => { 88 | if (exportError) { throw exportError; } 89 | 90 | fs.readFile(`${outputPath}routes.txt`, (readRoutesError, routesTxt) => { 91 | if (readRoutesError) { throw readRoutesError; } 92 | 93 | expect(String(routesTxt)).to.equal( 94 | 'route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,' + 95 | 'route_text_color,route_sort_order,tts_route_short_name,tts_route_long_name,temp\r\n' + 96 | 'route_0,agency_0,R0,Route 0,,3,,,,,r0,rooouuuteee 0,"{""key"":""value""}"\r\n' + 97 | 'route_x,agency_0,RX,"""Route X""",,3,,,,,,,"{""key"":""value""}"\r\n' + 98 | 'route_utf8,agency_0,RÛTF8,route_😎êωn → ∞⠁⠧⠑ ⠼éöÿΚαλημέρα\'´`,,3,,,,,旱獺屬,,"{""key"":""value""}"\r\n' + 99 | 'route_y,agency_0,RY,"{""routeLongName"":""""}",,3,,,,,,,"{""key"":""value""}"' 100 | ); 101 | 102 | fs.remove(outputPath, (removeError) => { 103 | if (removeError) { throw removeError; } 104 | 105 | done(); 106 | }); 107 | }); 108 | }); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /tests/test_shapes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { expect } = require('chai'); 4 | const { Gtfs } = require('../index'); 5 | 6 | describe('Tests on GTFS shapes', () => { 7 | it('Tests on shapes functions', (done) => { 8 | const path = `${__dirname}/samples/1`; 9 | const gtfs = new Gtfs(path); 10 | 11 | expect(sortedKeys(gtfs.getIndexedShapePoints())).to.deep.equal(['shape_0']); 12 | 13 | const trip0 = gtfs.getTripWithId('trip_0'); 14 | expect(sortedKeys(gtfs.getShapePointByShapePointSequenceOfTrip(trip0))).to.deep.equal(['1', '2']); 15 | expect(sortedKeys(gtfs.getShapePointByShapePointSequenceOfShapeId('shape_0'))).to.deep.equal(['1', '2']); 16 | 17 | const shapePoint1 = gtfs.getShapePointWithShapeIdAndShapePointSequence('shape_0', '1'); 18 | const shapePoint2 = gtfs.getShapePointWithShapeIdAndShapePointSequence('shape_0', '2'); 19 | expect(shapePoint1.shape_dist_traveled).to.equal('0'); 20 | expect(shapePoint2.shape_dist_traveled).to.equal('10'); 21 | 22 | gtfs.addShapePoint({ shape_id: 'shape_0', shape_pt_sequence: '3', shape_dist_traveled: '100' }); 23 | expect(sortedKeys(gtfs.getShapePointByShapePointSequenceOfTrip(trip0))).to.deep.equal(['1', '2', '3']); 24 | 25 | gtfs.addShapePoints([ 26 | { shape_id: 'shape_0', shape_pt_sequence: '4', shape_dist_traveled: '1000' }, 27 | { shape_id: 'shape_0', shape_pt_sequence: '5', shape_dist_traveled: '10000' }, 28 | ]); 29 | expect(sortedKeys(gtfs.getShapePointByShapePointSequenceOfTrip(trip0))).to.deep.equal(['1', '2', '3', '4', '5']); 30 | 31 | gtfs.removeShapePoint(gtfs.getShapePointWithShapeIdAndShapePointSequence('shape_0', '3')); 32 | expect(sortedKeys(gtfs.getShapePointByShapePointSequenceOfTrip(trip0))).to.deep.equal(['1', '2', '4', '5']); 33 | 34 | gtfs.removeShapePoints([ 35 | gtfs.getShapePointWithShapeIdAndShapePointSequence('shape_0', '2'), 36 | gtfs.getShapePointWithShapeIdAndShapePointSequence('shape_0', '5'), 37 | ]); 38 | expect(sortedKeys(gtfs.getShapePointByShapePointSequenceOfTrip(trip0))).to.deep.equal(['1', '4']); 39 | 40 | gtfs.setIndexedShapePoints(new Map([ 41 | ['shape_0', new Map([ 42 | ['1', { shape_id: 'shape_0', shape_pt_sequence: '1', shape_dist_traveled: '0' }], 43 | ['2', { shape_id: 'shape_0', shape_pt_sequence: '2', shape_dist_traveled: '20' }], 44 | ])], 45 | ['shape_1', new Map([ 46 | ['6', { shape_id: 'shape_1', shape_pt_sequence: '6', shape_dist_traveled: '0' }], 47 | ['7', { shape_id: 'shape_1', shape_pt_sequence: '7', shape_dist_traveled: '21' }], 48 | ])], 49 | ])); 50 | expect(sortedKeys(gtfs.getIndexedShapePoints())).to.deep.equal(['shape_0', 'shape_1']); 51 | expect(sortedKeys(gtfs.getShapePointByShapePointSequenceOfTrip({ shape_id: 'shape_0' }))).to.deep.equal(['1', '2']); 52 | expect(sortedKeys(gtfs.getShapePointByShapePointSequenceOfTrip({ shape_id: 'shape_1' }))).to.deep.equal(['6', '7']); 53 | 54 | const shapeDistanceTraveled = []; 55 | gtfs.forEachShapePoint((shapePoint) => { 56 | shapeDistanceTraveled.push(shapePoint.shape_dist_traveled); 57 | }); 58 | expect(shapeDistanceTraveled.sort()).to.deep.equal(['0', '0', '20', '21']); 59 | 60 | done(); 61 | }); 62 | 63 | it('Tests on gtfs.forEachShapePointOfShapeId(shapeId, iterator)', (done) => { 64 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 65 | 66 | gtfs.setIndexedShapePoints(new Map([ 67 | ['shape_0', new Map([ 68 | ['1', { shape_id: 'shape_0', shape_pt_sequence: '1', shape_dist_traveled: '0' }], 69 | ['2', { shape_id: 'shape_0', shape_pt_sequence: '2', shape_dist_traveled: '20' }], 70 | ])], 71 | ['shape_1', new Map([ 72 | ['6', { shape_id: 'shape_1', shape_pt_sequence: '6', shape_dist_traveled: '0' }], 73 | ['7', { shape_id: 'shape_1', shape_pt_sequence: '7', shape_dist_traveled: '21' }], 74 | ])], 75 | ])); 76 | 77 | const shapePointSequencesForShape1 = []; 78 | gtfs.forEachShapePointOfShapeId('shape_1', (calendarDates) => { 79 | shapePointSequencesForShape1.push(calendarDates.shape_pt_sequence); 80 | }); 81 | expect(shapePointSequencesForShape1.sort()).to.deep.equal(['6', '7']); 82 | 83 | done(); 84 | }); 85 | 86 | it('Tests on gtfs.resetShapePoints()', (done) => { 87 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 88 | 89 | expect(gtfs.getIndexedShapePoints().size).to.equal(1); 90 | 91 | gtfs.resetShapePoints(); 92 | 93 | expect(gtfs.getIndexedShapePoints().size).to.equal(0); 94 | 95 | done(); 96 | }); 97 | }); 98 | 99 | function sortedKeys(map) { 100 | return Array.from(map.keys()).sort(); 101 | } 102 | -------------------------------------------------------------------------------- /helpers/schema.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const version = '2017.12.11'; 4 | 5 | const keysByTableName = { 6 | agency: [ 7 | 'agency_id', 8 | 'agency_name', 9 | 'agency_url', 10 | 'agency_timezone', 11 | 'agency_lang', 12 | 'agency_phone', 13 | 'agency_fare_url', 14 | 'agency_email', 15 | ], 16 | stops: [ 17 | 'stop_id', 18 | 'stop_code', 19 | 'stop_name', 20 | 'stop_desc', 21 | 'stop_lat', 22 | 'stop_lon', 23 | 'zone_id', 24 | 'stop_url', 25 | 'location_type', 26 | 'parent_station', 27 | 'stop_timezone', 28 | 'wheelchair_boarding', 29 | 'tts_stop_name', 30 | ], 31 | routes: [ 32 | 'route_id', 33 | 'agency_id', 34 | 'route_short_name', 35 | 'route_long_name', 36 | 'route_desc', 37 | 'route_type', 38 | 'route_url', 39 | 'route_color', 40 | 'route_text_color', 41 | 'route_sort_order', 42 | 'tts_route_short_name', 43 | 'tts_route_long_name', 44 | ], 45 | trips: [ 46 | 'route_id', 47 | 'service_id', 48 | 'trip_id', 49 | 'trip_headsign', 50 | 'trip_short_name', 51 | 'direction_id', 52 | 'block_id', 53 | 'shape_id', 54 | 'wheelchair_accessible', 55 | 'bikes_allowed', 56 | ], 57 | stop_times: [ 58 | 'trip_id', 59 | 'arrival_time', 60 | 'departure_time', 61 | 'stop_id', 62 | 'stop_sequence', 63 | 'stop_headsign', 64 | 'pickup_type', 65 | 'drop_off_type', 66 | 'shape_dist_traveled', 67 | 'timepoint', 68 | ], 69 | calendar: [ 70 | 'service_id', 71 | 'monday', 72 | 'tuesday', 73 | 'wednesday', 74 | 'thursday', 75 | 'friday', 76 | 'saturday', 77 | 'sunday', 78 | 'start_date', 79 | 'end_date', 80 | ], 81 | calendar_dates: [ 82 | 'service_id', 83 | 'date', 84 | 'exception_type', 85 | ], 86 | fare_attributes: [ 87 | 'fare_id', 88 | 'price', 89 | 'currency_type', 90 | 'payment_method', 91 | 'transfers', 92 | 'agency_id', 93 | 'transfer_duration', 94 | ], 95 | fare_rules: [ 96 | 'fare_id', 97 | 'route_id', 98 | 'origin_id', 99 | 'destination_id', 100 | 'contains_id', 101 | ], 102 | shapes: [ 103 | 'shape_id', 104 | 'shape_pt_lat', 105 | 'shape_pt_lon', 106 | 'shape_pt_sequence', 107 | 'shape_dist_traveled', 108 | ], 109 | frequencies: [ 110 | 'trip_id', 111 | 'start_time', 112 | 'end_time', 113 | 'headway_secs', 114 | 'exact_times', 115 | ], 116 | transfers: [ 117 | 'from_stop_id', 118 | 'to_stop_id', 119 | 'transfer_type', 120 | 'min_transfer_time', 121 | ], 122 | pathways: [ 123 | 'pathway_id', 124 | 'from_stop_id', 125 | 'to_stop_id', 126 | 'pathway_mode', 127 | 'is_bidirectional', 128 | 'length', 129 | 'traversal_time', 130 | 'stair_count', 131 | 'max_slope', 132 | 'min_width', 133 | 'signposted_as', 134 | 'reversed_signposted_as', 135 | ], 136 | feed_info: [ 137 | 'feed_publisher_name', 138 | 'feed_publisher_url', 139 | 'feed_lang', 140 | 'feed_start_date', 141 | 'feed_end_date', 142 | 'feed_version', 143 | ], 144 | }; 145 | 146 | const indexKeysByTableName = { 147 | agency: { indexKey: 'agency_id' }, 148 | calendar: { indexKey: 'service_id' }, 149 | calendar_dates: { firstIndexKey: 'service_id', secondIndexKey: 'date' }, 150 | fare_attributes: { indexKey: 'fare_id' }, 151 | fare_rules: { setOfItems: true }, 152 | frequencies: { firstIndexKey: 'trip_id', secondIndexKey: 'start_time' }, 153 | routes: { indexKey: 'route_id' }, 154 | stop_times: { firstIndexKey: 'trip_id', secondIndexKey: 'stop_sequence' }, 155 | stops: { indexKey: 'stop_id' }, 156 | trips: { indexKey: 'trip_id' }, 157 | shapes: { firstIndexKey: 'shape_id', secondIndexKey: 'shape_pt_sequence' }, 158 | transfers: { firstIndexKey: 'from_stop_id', secondIndexKey: 'to_stop_id' }, 159 | pathways: { indexKey: 'pathway_id' }, 160 | feed_info: { singleton: true }, 161 | }; 162 | 163 | const tableNames = Object.keys(indexKeysByTableName); 164 | 165 | const deepnessByTableName = tableNames.reduce((accumulator, tableName) => { 166 | const indexKeys = indexKeysByTableName[tableName]; 167 | 168 | if (indexKeys.singleton || indexKeys.setOfItems) { 169 | accumulator[tableName] = 0; 170 | } else if (indexKeys.indexKey) { 171 | accumulator[tableName] = 1; 172 | } else if (indexKeys.firstIndexKey && indexKeys.secondIndexKey) { 173 | accumulator[tableName] = 2; 174 | } 175 | 176 | return accumulator; 177 | }, {}); 178 | 179 | module.exports = { 180 | deepnessByTableName, 181 | indexKeysByTableName, 182 | keysByTableName, 183 | tableNames, 184 | version, 185 | }; 186 | -------------------------------------------------------------------------------- /tests/test_fare_attributes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { expect } = require('chai'); 4 | const { Gtfs } = require('../index'); 5 | 6 | describe('Tests on GTFS fare attributes', () => { 7 | it('Tests on gtfs.getNumberOfFareAttributes(), .add…(), .remove…() and .getFareAttributeWithId', (done) => { 8 | const path = `${__dirname}/samples/1`; 9 | const gtfs = new Gtfs(path); 10 | const fareAttribute1 = buildFareAttribute(1); 11 | const fareAttribute2 = buildFareAttribute(2); 12 | const fareAttribute3 = buildFareAttribute(3); 13 | 14 | expect(gtfs.getNumberOfFareAttributes()).to.equal(0); 15 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.1') !== undefined).to.equal(false); 16 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.2') !== undefined).to.equal(false); 17 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.3') !== undefined).to.equal(false); 18 | 19 | gtfs.addFareAttributes([fareAttribute1, fareAttribute2]); 20 | 21 | expect(gtfs.getNumberOfFareAttributes()).to.equal(2); 22 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.1') !== undefined).to.equal(true); 23 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.2') !== undefined).to.equal(true); 24 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.3') !== undefined).to.equal(false); 25 | 26 | gtfs.addFareAttribute(fareAttribute3); 27 | 28 | expect(gtfs.getNumberOfFareAttributes()).to.equal(3); 29 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.1') !== undefined).to.equal(true); 30 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.2') !== undefined).to.equal(true); 31 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.3') !== undefined).to.equal(true); 32 | 33 | gtfs.removeFareAttribute(fareAttribute2); 34 | 35 | expect(gtfs.getNumberOfFareAttributes()).to.equal(2); 36 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.1') !== undefined).to.equal(true); 37 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.2') !== undefined).to.equal(false); 38 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.3') !== undefined).to.equal(true); 39 | 40 | gtfs.removeFareAttributes([fareAttribute1, fareAttribute3]); 41 | 42 | expect(gtfs.getNumberOfFareAttributes()).to.equal(0); 43 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.1') !== undefined).to.equal(false); 44 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.2') !== undefined).to.equal(false); 45 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.3') !== undefined).to.equal(false); 46 | 47 | done(); 48 | }); 49 | 50 | it('Tests on gtfs.setFareAttributes() and .resetFareAttributes', (done) => { 51 | const path = `${__dirname}/samples/1`; 52 | const gtfs = new Gtfs(path); 53 | const fareAttribute1 = buildFareAttribute(1); 54 | const fareAttribute2 = buildFareAttribute(2); 55 | 56 | expect(gtfs.getNumberOfFareAttributes()).to.equal(0); 57 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.1') !== undefined).to.equal(false); 58 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.2') !== undefined).to.equal(false); 59 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.3') !== undefined).to.equal(false); 60 | 61 | gtfs.setIndexedFareAttributes(new Map([ 62 | ['fareAttribute.fare_id.1', fareAttribute1], 63 | ['fareAttribute.fare_id.2', fareAttribute2], 64 | ])); 65 | 66 | expect(gtfs.getNumberOfFareAttributes()).to.equal(2); 67 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.1') !== undefined).to.equal(true); 68 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.2') !== undefined).to.equal(true); 69 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.3') !== undefined).to.equal(false); 70 | 71 | gtfs.resetFareAttributes(); 72 | 73 | expect(gtfs.getNumberOfFareAttributes()).to.equal(0); 74 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.1') !== undefined).to.equal(false); 75 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.2') !== undefined).to.equal(false); 76 | expect(gtfs.getFareAttributeWithId('fareAttribute.fare_id.3') !== undefined).to.equal(false); 77 | 78 | done(); 79 | }); 80 | 81 | it('Tests on gtfs.forEachFareAttribute()', (done) => { 82 | const path = `${__dirname}/samples/1`; 83 | const gtfs = new Gtfs(path); 84 | gtfs.addFareAttributes([buildFareAttribute(1), buildFareAttribute(3)]); 85 | 86 | const fareIds = []; 87 | gtfs.forEachFareAttribute(fareAttribute => fareIds.push(fareAttribute.fare_id)); 88 | 89 | expect(fareIds.sort()).to.deep.equal(['fareAttribute.fare_id.1', 'fareAttribute.fare_id.3']); 90 | 91 | done(); 92 | }); 93 | 94 | it('Tests on gtfs.getIndexedFareAttributes()', (done) => { 95 | const path = `${__dirname}/samples/1`; 96 | const gtfs = new Gtfs(path); 97 | gtfs.addFareAttributes([buildFareAttribute(1), buildFareAttribute(3)]); 98 | 99 | const fareIds = []; 100 | const indexedFareAttributes = gtfs.getIndexedFareAttributes(); 101 | indexedFareAttributes.forEach(fareAttribute => fareIds.push(fareAttribute.fare_id)); 102 | 103 | expect(fareIds.sort()).to.deep.equal(['fareAttribute.fare_id.1', 'fareAttribute.fare_id.3']); 104 | 105 | done(); 106 | }); 107 | }); 108 | 109 | function buildFareAttribute(integer) { 110 | return { 111 | 'fare_id': `fareAttribute.fare_id.${integer}`, 112 | 'price': `fareAttribute.price.${integer}`, 113 | 'currency_type': `fareAttribute.currency_type.${integer}`, 114 | 'payment_method': `fareAttribute.payment_method.${integer}`, 115 | 'transfers': `fareAttribute.transfers.${integer}`, 116 | 'agency_id': `fareAttribute.agency_id.${integer}`, 117 | 'transfer_duration': `fareAttribute.transfer_duration.${integer}`, 118 | }; 119 | } 120 | -------------------------------------------------------------------------------- /tests/test_calendar_dates.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { expect } = require('chai'); 4 | const { Gtfs } = require('../index'); 5 | 6 | describe('Tests on GTFS calendar dates', () => { 7 | it('Tests on calendar dates functions', (done) => { 8 | const path = `${__dirname}/samples/1`; 9 | const gtfs = new Gtfs(path); 10 | 11 | expect(sortedKeys(gtfs.getIndexedCalendarDates())).to.deep.equal(['service_0']); 12 | 13 | const trip0 = gtfs.getTripWithId('trip_0'); 14 | expect(sortedKeys(gtfs.getCalendarDateByDateOfTrip(trip0))).to.deep.equal(['20171228', '20171231']); 15 | expect(sortedKeys(gtfs.getCalendarDateByDateOfServiceId('service_0'))).to.deep.equal(['20171228', '20171231']); 16 | 17 | const calendarDate28 = gtfs.getCalendarDateWithServiceIdAndDate('service_0', '20171228'); 18 | const calendarDate31 = gtfs.getCalendarDateWithServiceIdAndDate('service_0', '20171231'); 19 | expect(calendarDate28.exception_type).to.equal('1'); 20 | expect(calendarDate31.exception_type).to.equal('2'); 21 | 22 | gtfs.addCalendarDate({ service_id: 'service_0', date: '20180101', exception_type: '2' }); 23 | expect(sortedKeys(gtfs.getCalendarDateByDateOfTrip(trip0))).to.deep.equal(['20171228', '20171231', '20180101']); 24 | 25 | gtfs.addCalendarDates([ 26 | { service_id: 'service_0', date: '20180102', exception_type: '1' }, 27 | { service_id: 'service_0', date: '20180103', exception_type: '1' }, 28 | ]); 29 | const expectedDates1 = ['20171228', '20171231', '20180101', '20180102', '20180103']; 30 | expect(sortedKeys(gtfs.getCalendarDateByDateOfTrip(trip0))).to.deep.equal(expectedDates1); 31 | 32 | gtfs.removeCalendarDate(gtfs.getCalendarDateWithServiceIdAndDate('service_0', '20180101')); 33 | const expectedDates2 = ['20171228', '20171231', '20180102', '20180103']; 34 | expect(sortedKeys(gtfs.getCalendarDateByDateOfTrip(trip0))).to.deep.equal(expectedDates2); 35 | 36 | gtfs.removeCalendarDates([ 37 | gtfs.getCalendarDateWithServiceIdAndDate('service_0', '20171228'), 38 | gtfs.getCalendarDateWithServiceIdAndDate('service_0', '20180102'), 39 | ]); 40 | expect(sortedKeys(gtfs.getCalendarDateByDateOfTrip(trip0))).to.deep.equal(['20171231', '20180103']); 41 | 42 | gtfs.setIndexedCalendarDates(new Map([ 43 | ['service_0', new Map([ 44 | ['20171228', { trip_id: 'service_0', date: '20171228', exception_type: '1' }], 45 | ['20171231', { trip_id: 'service_0', date: '20171231', exception_type: '2' }], 46 | ])], 47 | ['service_1', new Map([ 48 | ['20180101', { trip_id: 'service_1', date: '20180101', exception_type: '2' }], 49 | ['20180102', { trip_id: 'service_1', date: '20180102', exception_type: '1' }], 50 | ])], 51 | ])); 52 | expect(sortedKeys(gtfs.getIndexedCalendarDates())).to.deep.equal(['service_0', 'service_1']); 53 | const expectedDates3 = ['20171228', '20171231']; 54 | expect(sortedKeys(gtfs.getCalendarDateByDateOfTrip({ service_id: 'service_0' }))).to.deep.equal(expectedDates3); 55 | const expectedDates4 = ['20180101', '20180102']; 56 | expect(sortedKeys(gtfs.getCalendarDateByDateOfTrip({ service_id: 'service_1' }))).to.deep.equal(expectedDates4); 57 | 58 | const exceptionsTypes = []; 59 | gtfs.forEachCalendarDate((calendarDate) => { 60 | exceptionsTypes.push(calendarDate.exception_type); 61 | }); 62 | expect(exceptionsTypes.sort()).to.deep.equal(['1', '1', '2', '2']); 63 | 64 | done(); 65 | }); 66 | 67 | it('Tests on gtfs.forEachCalendarDateOfTrip(trip, iterator)', (done) => { 68 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 69 | 70 | gtfs.setIndexedCalendarDates(new Map([ 71 | ['service_0', new Map([ 72 | ['20171228', { trip_id: 'service_0', date: '20171228', exception_type: '1' }], 73 | ['20171231', { trip_id: 'service_0', date: '20171231', exception_type: '2' }], 74 | ])], 75 | ['service_1', new Map([ 76 | ['20180101', { trip_id: 'service_1', date: '20180101', exception_type: '3' }], 77 | ['20180102', { trip_id: 'service_1', date: '20180102', exception_type: '4' }], 78 | ])], 79 | ])); 80 | 81 | const exceptionTypesForService1 = []; 82 | gtfs.forEachCalendarDateOfTrip({ service_id: 'service_1' }, (calendarDates) => { 83 | exceptionTypesForService1.push(calendarDates.exception_type); 84 | }); 85 | expect(exceptionTypesForService1.sort()).to.deep.equal(['3', '4']); 86 | 87 | done(); 88 | }); 89 | 90 | it('Tests on gtfs.forEachCalendarDateWithServiceId(serviceId, iterator)', (done) => { 91 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 92 | 93 | gtfs.setIndexedCalendarDates(new Map([ 94 | ['service_0', new Map([ 95 | ['20171228', { trip_id: 'service_0', date: '20171228', exception_type: '1' }], 96 | ['20171231', { trip_id: 'service_0', date: '20171231', exception_type: '2' }], 97 | ])], 98 | ['service_1', new Map([ 99 | ['20180101', { trip_id: 'service_1', date: '20180101', exception_type: '3' }], 100 | ['20180102', { trip_id: 'service_1', date: '20180102', exception_type: '4' }], 101 | ])], 102 | ])); 103 | 104 | const exceptionTypesForService1 = []; 105 | gtfs.forEachCalendarDateWithServiceId('service_1', (calendarDates) => { 106 | exceptionTypesForService1.push(calendarDates.exception_type); 107 | }); 108 | expect(exceptionTypesForService1.sort()).to.deep.equal(['3', '4']); 109 | 110 | done(); 111 | }); 112 | 113 | it('Tests on gtfs.hasCalendarDatesWithServiceId(serviceId, iterator)', (done) => { 114 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 115 | 116 | expect(gtfs.hasCalendarDatesWithServiceId('service_1')).to.equal(false); 117 | 118 | gtfs.setIndexedCalendarDates(new Map([ 119 | ['service_0', new Map([ 120 | ['20171228', { trip_id: 'service_0', date: '20171228', exception_type: '1' }], 121 | ['20171231', { trip_id: 'service_0', date: '20171231', exception_type: '2' }], 122 | ])], 123 | ['service_1', new Map([ 124 | ['20180101', { trip_id: 'service_1', date: '20180101', exception_type: '2' }], 125 | ['20180102', { trip_id: 'service_1', date: '20180102', exception_type: '1' }], 126 | ])], 127 | ])); 128 | 129 | expect(gtfs.hasCalendarDatesWithServiceId('service_1')).to.equal(true); 130 | 131 | done(); 132 | }); 133 | 134 | it('Tests on gtfs.resetCalendarDates()', (done) => { 135 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 136 | 137 | expect(gtfs.getIndexedCalendarDates().size).to.equal(1); 138 | 139 | gtfs.resetCalendarDates(); 140 | 141 | expect(gtfs.getIndexedCalendarDates().size).to.equal(0); 142 | 143 | done(); 144 | }); 145 | }); 146 | 147 | function sortedKeys(map) { 148 | return Array.from(map.keys()).sort(); 149 | } 150 | -------------------------------------------------------------------------------- /tests/test_stop_times.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { expect } = require('chai'); 4 | const { Gtfs } = require('../index'); 5 | 6 | describe('Tests on GTFS stop times', () => { 7 | it('Tests on stop times functions', (done) => { 8 | const path = `${__dirname}/samples/1`; 9 | const gtfs = new Gtfs(path); 10 | 11 | expect(sortedKeys(gtfs.getIndexedStopTimes())).to.deep.equal(['trip_0']); 12 | 13 | const trip0 = gtfs.getTripWithId('trip_0'); 14 | expect(sortedKeys(gtfs.getStopTimeByStopSequenceOfTrip(trip0))).to.deep.equal(['0', '1']); 15 | 16 | const stopTime0 = gtfs.getStopTimeWithTripIdAndStopSequence('trip_0', '0'); 17 | const stopTime1 = gtfs.getStopTimeWithTripIdAndStopSequence('trip_0', '1'); 18 | expect(stopTime0.stop_headsign).to.equal('Stop Headsign 0'); 19 | expect(stopTime1.stop_headsign).to.equal('Stop Headsign 1'); 20 | 21 | gtfs.addStopTime({ trip_id: 'trip_0', stop_id: 'stop_0', stop_sequence: '2', stop_headsign: 'Stop Headsign 2' }); 22 | expect(sortedKeys(gtfs.getStopTimeByStopSequenceOfTrip(trip0))).to.deep.equal(['0', '1', '2']); 23 | 24 | gtfs.addStopTimes([ 25 | { trip_id: 'trip_0', stop_id: 'stop_1', stop_sequence: '3', stop_headsign: 'Stop Headsign 3' }, 26 | { trip_id: 'trip_0', stop_id: 'stop_0', stop_sequence: '4', stop_headsign: 'Stop Headsign 4' }, 27 | ]); 28 | expect(sortedKeys(gtfs.getStopTimeByStopSequenceOfTrip(trip0))).to.deep.equal(['0', '1', '2', '3', '4']); 29 | 30 | gtfs.removeStopTime(gtfs.getStopTimeWithTripIdAndStopSequence('trip_0', '2')); 31 | expect(sortedKeys(gtfs.getStopTimeByStopSequenceOfTrip(trip0))).to.deep.equal(['0', '1', '3', '4']); 32 | 33 | gtfs.removeStopTimes([ 34 | gtfs.getStopTimeWithTripIdAndStopSequence('trip_0', '0'), 35 | gtfs.getStopTimeWithTripIdAndStopSequence('trip_0', '3'), 36 | ]); 37 | expect(sortedKeys(gtfs.getStopTimeByStopSequenceOfTrip(trip0))).to.deep.equal(['1', '4']); 38 | 39 | gtfs.setIndexedStopTimes(new Map([ 40 | ['trip_0', new Map([ 41 | ['0', { trip_id: 'trip_0', stop_id: 'stop_0', stop_sequence: '0', stop_headsign: 'Stop Headsign 000' }], 42 | ['1', { trip_id: 'trip_0', stop_id: 'stop_1', stop_sequence: '1', stop_headsign: 'Stop Headsign 011' }], 43 | ])], 44 | ['trip_1', new Map([ 45 | ['5', { trip_id: 'trip_1', stop_id: 'stop_1', stop_sequence: '5', stop_headsign: 'Stop Headsign 115' }], 46 | ['6', { trip_id: 'trip_1', stop_id: 'stop_0', stop_sequence: '6', stop_headsign: 'Stop Headsign 106' }], 47 | ])], 48 | ])); 49 | expect(sortedKeys(gtfs.getIndexedStopTimes())).to.deep.equal(['trip_0', 'trip_1']); 50 | expect(sortedKeys(gtfs.getStopTimeByStopSequenceOfTrip({ trip_id: 'trip_0' }))).to.deep.equal(['0', '1']); 51 | expect(sortedKeys(gtfs.getStopTimeByStopSequenceOfTrip({ trip_id: 'trip_1' }))).to.deep.equal(['5', '6']); 52 | 53 | const stopHeadsigns = []; 54 | gtfs.forEachStopTime((stopTime) => { 55 | stopHeadsigns.push(stopTime.stop_headsign); 56 | }); 57 | const expectedStopHeadsigns = ['Stop Headsign 000', 'Stop Headsign 011', 'Stop Headsign 106', 'Stop Headsign 115']; 58 | expect(stopHeadsigns.sort()).to.deep.equal(expectedStopHeadsigns); 59 | 60 | const stopHeadsignsOfTrip0 = []; 61 | gtfs.forEachStopTimeOfTrip({ trip_id: 'trip_0' }, (stopTime) => { 62 | stopHeadsignsOfTrip0.push(stopTime.stop_headsign); 63 | }); 64 | const expectedStopHeadsignsOfTrip0 = ['Stop Headsign 000', 'Stop Headsign 011']; 65 | expect(stopHeadsignsOfTrip0.sort()).to.deep.equal(expectedStopHeadsignsOfTrip0); 66 | 67 | done(); 68 | }); 69 | 70 | it('Tests on gtfs.forEachStopTimeOfTripId(tripId, iterator)', (done) => { 71 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 72 | 73 | gtfs.setIndexedStopTimes(new Map([ 74 | ['trip_0', new Map([ 75 | ['0', { trip_id: 'trip_0', stop_id: 'stop_0', stop_sequence: '0', stop_headsign: 'Stop Headsign 000' }], 76 | ['1', { trip_id: 'trip_0', stop_id: 'stop_1', stop_sequence: '1', stop_headsign: 'Stop Headsign 011' }], 77 | ])], 78 | ['trip_1', new Map([ 79 | ['5', { trip_id: 'trip_1', stop_id: 'stop_1', stop_sequence: '5', stop_headsign: 'Stop Headsign 115' }], 80 | ['6', { trip_id: 'trip_1', stop_id: 'stop_0', stop_sequence: '6', stop_headsign: 'Stop Headsign 106' }], 81 | ])], 82 | ])); 83 | 84 | const stopHeadsignsOfTrip0 = []; 85 | gtfs.forEachStopTimeOfTripId('trip_0', (stopTime) => { 86 | stopHeadsignsOfTrip0.push(stopTime.stop_headsign); 87 | }); 88 | const expectedStopHeadsigns = ['Stop Headsign 000', 'Stop Headsign 011']; 89 | expect(stopHeadsignsOfTrip0.sort()).to.deep.equal(expectedStopHeadsigns); 90 | 91 | done(); 92 | }); 93 | 94 | it('Tests on gtfs.getNumberOfStopTimeOfTrip(trip)', (done) => { 95 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 96 | 97 | gtfs.setIndexedStopTimes(new Map([ 98 | ['trip_0', new Map([ 99 | ['0', { trip_id: 'trip_0', stop_id: 'stop_0', stop_sequence: '0', stop_headsign: 'Stop Headsign 000' }], 100 | ['1', { trip_id: 'trip_0', stop_id: 'stop_1', stop_sequence: '1', stop_headsign: 'Stop Headsign 011' }], 101 | ])], 102 | ['trip_1', new Map([ 103 | ['5', { trip_id: 'trip_1', stop_id: 'stop_1', stop_sequence: '5', stop_headsign: 'Stop Headsign 115' }], 104 | ['6', { trip_id: 'trip_1', stop_id: 'stop_0', stop_sequence: '6', stop_headsign: 'Stop Headsign 106' }], 105 | ])], 106 | ])); 107 | 108 | expect(gtfs.getNumberOfStopTimeOfTrip({ trip_id: 'trip_1' })).to.equal(2); 109 | 110 | done(); 111 | }); 112 | 113 | it('Tests on gtfs.getNumberOfStopTimeOfTripId(tripId)', (done) => { 114 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 115 | 116 | gtfs.setIndexedStopTimes(new Map([ 117 | ['trip_0', new Map([ 118 | ['0', { trip_id: 'trip_0', stop_id: 'stop_0', stop_sequence: '0', stop_headsign: 'Stop Headsign 000' }], 119 | ['1', { trip_id: 'trip_0', stop_id: 'stop_1', stop_sequence: '1', stop_headsign: 'Stop Headsign 011' }], 120 | ])], 121 | ['trip_1', new Map([ 122 | ['5', { trip_id: 'trip_1', stop_id: 'stop_1', stop_sequence: '5', stop_headsign: 'Stop Headsign 115' }], 123 | ['6', { trip_id: 'trip_1', stop_id: 'stop_0', stop_sequence: '6', stop_headsign: 'Stop Headsign 106' }], 124 | ])], 125 | ])); 126 | 127 | expect(gtfs.getNumberOfStopTimeOfTripId('trip_1')).to.equal(2); 128 | 129 | done(); 130 | }); 131 | 132 | it('Tests on gtfs.resetStopTimes()', (done) => { 133 | const gtfs = new Gtfs(`${__dirname}/samples/1`); 134 | 135 | expect(gtfs.getIndexedStopTimes().size).to.equal(1); 136 | expect(gtfs.getNumberOfStopTimeOfTripId('trip_0')).to.equal(2); 137 | 138 | gtfs.resetStopTimes(); 139 | 140 | expect(gtfs.getIndexedStopTimes().size).to.equal(0); 141 | expect(gtfs.getNumberOfStopTimeOfTripId('trip_0')).to.equal(0); 142 | 143 | done(); 144 | }); 145 | }); 146 | 147 | function sortedKeys(map) { 148 | return Array.from(map.keys()).sort(); 149 | } 150 | -------------------------------------------------------------------------------- /helpers/export.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable no-underscore-dangle */ 4 | 5 | const async = require('async'); 6 | const infoLog = require('debug')('gtfsNodeLib:i'); 7 | const warningLog = require('debug')('gtfsNodeLib:w'); 8 | const errorLog = require('debug')('gtfsNodeLib:e'); 9 | const fs = require('fs-extra'); 10 | const Papa = require('papaparse'); 11 | 12 | /** 13 | * Private functions 14 | */ 15 | 16 | function getHHmmss() { 17 | const date = new Date(); 18 | return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; 19 | } 20 | 21 | function resetOutputFolder(outputPath, callback) { 22 | fs.remove(outputPath, (removeError) => { 23 | if (removeError) { 24 | callback(removeError); 25 | return; 26 | } 27 | 28 | fs.mkdirp(outputPath, (makeDirectoryError) => { 29 | if (makeDirectoryError) { 30 | callback(makeDirectoryError); 31 | return; 32 | } 33 | 34 | callback(); 35 | }); 36 | }); 37 | } 38 | 39 | function copyUntouchedTable(inputPath, outputPath, tableName, callback) { 40 | const fullPathToInputFile = `${inputPath + tableName}.txt`; 41 | const fullPathToOutputFile = `${outputPath + tableName}.txt`; 42 | 43 | fs.open(fullPathToInputFile, 'r', (err) => { 44 | if (err && err.code === 'ENOENT') { 45 | warningLog(`[${getHHmmss()}] Table doesn't exist and won't be added: ${tableName}`); 46 | callback(); 47 | return; 48 | } 49 | if (err) { 50 | errorLog(err); 51 | callback(); 52 | return; 53 | } 54 | 55 | fs.copy(fullPathToInputFile, fullPathToOutputFile, (copyError) => { 56 | if (copyError) { 57 | errorLog(copyError); 58 | } 59 | 60 | infoLog(`[${getHHmmss()}] Table has been copied: ${tableName}`); 61 | callback(); 62 | }); 63 | }); 64 | } 65 | 66 | function exportTable(tableName, gtfs, outputPath, callback) { 67 | const actualKeys = gtfs.getActualKeysForTable(tableName); 68 | const firstRow = `${actualKeys.join(',')}`; 69 | const outputFullPath = `${outputPath + tableName}.txt`; 70 | 71 | fs.writeFile(outputFullPath, firstRow, (writeError) => { 72 | if (writeError) { 73 | throw writeError; 74 | } 75 | 76 | const indexKeys = gtfs._schema.indexKeysByTableName[tableName]; 77 | 78 | if (indexKeys.singleton) { 79 | let item = gtfs.getIndexedTable(tableName); 80 | if (!item) { 81 | callback(); 82 | return; 83 | } 84 | 85 | if (gtfs._preExportItemFunction) { 86 | item = gtfs._preExportItemFunction(item, tableName); 87 | } 88 | 89 | const formattedGtfsRowValues = getObjectValuesUsingKeyOrdering(item, actualKeys); 90 | const row = Papa.unparse({ 91 | fields: actualKeys, 92 | data: formattedGtfsRowValues, 93 | }, 94 | { 95 | header: false, 96 | }); 97 | 98 | fs.appendFile(outputFullPath, `\r\n${row}`, callback); 99 | return; 100 | } 101 | 102 | const deepness = gtfs._schema.deepnessByTableName[tableName]; 103 | 104 | let rows = []; 105 | 106 | /* 107 | About why the async wrapper is used inside the async.eachSeries: 108 | If the function async.eachSeries runs without doing anything, just calling the callback (which 109 | happens when there are a lot of empty objects), it crashes. It is a known bug of async. 110 | They don't fix it due to performance reasons (see Common Pitfalls - https://caolan.github.io/async/v3/) 111 | To deal with this, we simply wrap the possible asynchronous function with the keyword async. 112 | The ES2017 async functions are returned as-is. 113 | This is useful for preventing stack overflows (RangeError: Maximum call stack size exceeded), 114 | and generally keeping Zalgo contained. Hence, Async Functions are immune to Zalgo's corrupting influences, 115 | as they always resolve on a later tick. 116 | More info on Zalgo (https://blog.izs.me/2013/08/designing-apis-for-asynchrony) 117 | */ 118 | async.eachSeries(gtfs.getIndexedTable(tableName), async (iteratee) => { 119 | if (indexKeys.setOfItems) { 120 | let item = iteratee; 121 | if (gtfs._preExportItemFunction) { 122 | item = gtfs._preExportItemFunction(item, tableName); 123 | } 124 | 125 | const formattedGtfsRowValues = getObjectValuesUsingKeyOrdering(item, actualKeys); 126 | const row = Papa.unparse({ 127 | fields: actualKeys, 128 | data: formattedGtfsRowValues, 129 | }, 130 | { 131 | header: false, 132 | }); 133 | 134 | rows.push(`\r\n${row}`); 135 | } else { 136 | let [key, object] = iteratee; 137 | 138 | if (deepness === 0 || deepness === 1) { 139 | if (gtfs._preExportItemFunction) { 140 | object = gtfs._preExportItemFunction(object, tableName, key); 141 | } 142 | 143 | const formattedGtfsRowValues = getObjectValuesUsingKeyOrdering(object, actualKeys); 144 | const row = Papa.unparse({ 145 | fields: actualKeys, 146 | data: formattedGtfsRowValues, 147 | }, 148 | { 149 | header: false, 150 | }); 151 | 152 | rows.push(`\r\n${row}`); 153 | } else if (deepness === 2) { 154 | object.forEach((subObject, subKey) => { 155 | if (gtfs._preExportItemFunction) { 156 | subObject = gtfs._preExportItemFunction(subObject, tableName, key, subKey); 157 | } 158 | 159 | const formattedGtfsRowValues = getObjectValuesUsingKeyOrdering(subObject, actualKeys); 160 | const row = Papa.unparse({ 161 | fields: actualKeys, 162 | data: formattedGtfsRowValues, 163 | }, 164 | { 165 | header: false, 166 | }); 167 | 168 | rows.push(`\r\n${row}`); 169 | }); 170 | } 171 | } 172 | 173 | if (rows.length < 100) { 174 | return; 175 | } 176 | 177 | await fs.appendFile(outputFullPath, rows.join('')); 178 | rows = []; 179 | }, (asyncEachSeriesError) => { 180 | if (asyncEachSeriesError) { 181 | throw asyncEachSeriesError; 182 | } 183 | if (rows.length === 0) { 184 | infoLog(`[${getHHmmss()}] Table has been exported: ${tableName}`); 185 | callback(); 186 | return; 187 | } 188 | 189 | fs.appendFile(outputFullPath, rows.join(''), (appendingError) => { 190 | if (appendingError) { throw appendingError; } 191 | 192 | infoLog(`[${getHHmmss()}] Table has been exported: ${tableName}`); 193 | callback(); 194 | }); 195 | }); 196 | }); 197 | } 198 | 199 | function getObjectValuesUsingKeyOrdering(object, keys) { 200 | return keys.map((key) => { 201 | let value = object[key]; 202 | 203 | if (value === undefined || value === null) { 204 | return ''; 205 | } 206 | 207 | const type = typeof value; 208 | if (type === 'object') { 209 | value = JSON.stringify(value); 210 | } else if (type !== 'string') { 211 | value = String(value); 212 | } 213 | 214 | return value; 215 | }); 216 | } 217 | 218 | /** 219 | * Public function 220 | */ 221 | 222 | exports.exportGtfs = (gtfs, outputPath, callback) => { 223 | if (typeof outputPath !== 'string') { 224 | throw new Error(`Gtfs need a valid output path as string, instead of: "${outputPath}".`); 225 | } 226 | if (outputPath.match(/\/$/) === null) { 227 | outputPath += '/'; 228 | } 229 | 230 | resetOutputFolder(outputPath, (resetOutputFolderError) => { 231 | if (resetOutputFolderError) { 232 | callback(resetOutputFolderError); 233 | return; 234 | } 235 | 236 | infoLog(`Will start exportation of tables: ${Array.from(gtfs.getTableNames()).join(', ')}`); 237 | 238 | async.eachSeries(gtfs.getTableNames(), (tableName, done) => { 239 | if (gtfs._tables.has(tableName) === true) { 240 | infoLog(`[${getHHmmss()}] Table will be exported: ${tableName}`); 241 | exportTable(tableName, gtfs, outputPath, done); 242 | } else { 243 | infoLog(`[${getHHmmss()}] Table will be copied: ${tableName}`); 244 | copyUntouchedTable(gtfs.getPath(), outputPath, tableName, done); 245 | } 246 | }, callback); 247 | }); 248 | }; 249 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Run the tests with mocha: mocha tests.js */ 4 | 5 | // eslint-disable-next-line import/no-extraneous-dependencies 6 | const { expect } = require('chai'); 7 | const fs = require('fs-extra'); 8 | 9 | const { Gtfs } = require('../index'); 10 | 11 | describe('Tests on GTFS', () => { 12 | it('Test on meta functions', (done) => { 13 | const path = `${__dirname}/samples/1/`; 14 | const gtfs = new Gtfs(path); 15 | 16 | expect(gtfs.isGtfs).to.equal(true); 17 | expect(gtfs.getPath()).to.equal(path); 18 | 19 | done(); 20 | }); 21 | 22 | it('Tests on exporting', (done) => { 23 | const path = `${__dirname}/samples/1`; 24 | const gtfs = new Gtfs(path); 25 | 26 | gtfs.getFeedInfo().feed_lang = 'fr'; 27 | gtfs.getFeedInfo().some_extra_field = 'some_extra_value'; 28 | 29 | gtfs.forEachRoute((route) => { 30 | route.route_desc = 'Some new description'; 31 | route.some_extra_route_field = 'some_extra_route_value'; 32 | }); 33 | 34 | const outputPath = `${__dirname}/temp_4865ce67d01f96a489fbd0e71ad8800b/`; 35 | gtfs.exportAtPath(outputPath, (exportError) => { 36 | if (exportError) { throw exportError; } 37 | 38 | fs.readFile(`${outputPath}routes.txt`, (readRoutesError, routesTxt) => { 39 | if (readRoutesError) { throw readRoutesError; } 40 | 41 | // Test deepness 1 42 | expect(String(routesTxt)).to.equal( 43 | 'route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,' + 44 | 'route_text_color,route_sort_order,tts_route_short_name,tts_route_long_name,some_extra_route_field\r\n' + 45 | 'route_0,agency_0,R0,Route 0,Some new description,3,,,,,r0,rooouuuteee 0,some_extra_route_value\r\n' + 46 | 'route_x,agency_0,RX,"""Route X""",Some new description,3,,,,,,,some_extra_route_value\r\n' + 47 | 'route_utf8,agency_0,RÛTF8,route_😎êωn → ∞⠁⠧⠑ ⠼éöÿΚαλημέρα\'´`,' + 48 | 'Some new description,3,,,,,旱獺屬,,some_extra_route_value\r\n' + 49 | 'route_y,agency_0,RY,"{""routeLongName"":""""}",Some new description,3,,,,,,,some_extra_route_value' 50 | ); 51 | 52 | // Test singleton 53 | fs.readFile(`${outputPath}feed_info.txt`, (readFeedInfoError, feedInfoTxt) => { 54 | if (readFeedInfoError) { throw readFeedInfoError; } 55 | 56 | expect(String(feedInfoTxt)).to.equal( 57 | 'feed_publisher_name,feed_publisher_url,feed_lang,feed_start_date,feed_end_date,feed_version,' + 58 | 'some_extra_field\r\n' + 59 | 'Publisher Name,http://google.com,fr,20000101,21001231,42,some_extra_value' 60 | ); 61 | 62 | // Test deepness 2 63 | fs.readFile(`${outputPath}stop_times.txt`, (readStopTimesError, stopTimesTxt) => { 64 | if (readStopTimesError) { 65 | throw readStopTimesError; 66 | } 67 | 68 | expect(String(stopTimesTxt)).to.equal( 69 | 'trip_id,arrival_time,departure_time,stop_id,stop_sequence,pickup_type,drop_off_type,stop_headsign\n' + 70 | 'trip_0,10:00:00,10:00:00,stop_0,0,,,Stop Headsign 0\n' + 71 | 'trip_0,20:00:00,20:00:00,stop_1,1,,,Stop Headsign 1' 72 | ); 73 | 74 | fs.remove(outputPath, (removeError) => { 75 | if (removeError) { throw removeError; } 76 | 77 | done(); 78 | }); 79 | }); 80 | }); 81 | }); 82 | }); 83 | }); 84 | 85 | // it('Tests on importing/exporting very large file', (done) => { 86 | // You'll need to download a large CSV and place it within the samples/veryLargeFile folder. 87 | // A good one is STM stop_times.txt 88 | // const path = `${__dirname}/samples/veryLargeFile`; 89 | // const gtfs = new Gtfs(path); 90 | // 91 | // const stopTime = gtfs.getStopTimeWithTripIdAndStopSequence('197160641', '1'); 92 | // expect(stopTime.stop_id).to.equal('51095'); 93 | // 94 | // const outputPath = `${__dirname}/temp_4865ce67d01f96a489fbd0e71adef800b/`; 95 | // 96 | // gtfs.exportAtPath(outputPath, (exportError) => { 97 | // if (exportError) { throw exportError; } 98 | // 99 | // fs.remove(outputPath, (removeError) => { 100 | // if (removeError) { throw removeError; } 101 | // 102 | // done(); 103 | // }); 104 | // }); 105 | // }); 106 | 107 | it('Tests on the regex/pattern applied to fix a bad CSV', (done) => { 108 | const path = `${__dirname}/samples/2/`; 109 | const gtfsWithoutFix = new Gtfs(path); 110 | 111 | const stop0 = gtfsWithoutFix.getStopWithId('stop_0'); 112 | const stop1 = gtfsWithoutFix.getStopWithId('stop_1'); 113 | const stop2 = gtfsWithoutFix.getStopWithId('stop_2'); 114 | 115 | // Fixes too many fields 116 | expect(Object.keys(stop0).length).to.equal(Object.keys(stop1).length); 117 | 118 | // Fixes too few field 119 | expect(Object.keys(stop0).length).to.equal(Object.keys(stop2).length); 120 | 121 | // Fixes field using regexPatternObjectsByTableName 122 | const regexPatternObjectsByTableName = new Map([[ 123 | 'stops', [{ regex: /,Some "other" stop,/g, pattern: ',Some stop,' }], 124 | ]]); 125 | 126 | const gtfsWithFix = new Gtfs(path, { regexPatternObjectsByTableName }); 127 | 128 | expect(gtfsWithFix.getStopWithId('stop_0').stop_desc).to.equal('Some stop'); 129 | 130 | done(); 131 | }); 132 | 133 | it('Tests on bad decoding of UTF-8 characters when decoding by batch', (done) => { 134 | const path = `${__dirname}/samples/3/`; 135 | const gtfs = new Gtfs(path); 136 | 137 | expect(() => gtfs.getIndexedStops()).to.not.throw(); 138 | 139 | gtfs.forEachStop((stop) => { 140 | /* 141 | The stop_code of the samples/3 only contains the character ê. 142 | That character takes two bytes in UTF-8. 143 | If replacing ê by empty string still yields any character => there was an error decoding. 144 | */ 145 | const wronglyDecodedCharacters = stop.stop_code.replace(/ê*/g, ''); 146 | expect(wronglyDecodedCharacters.length).to.equals(0); 147 | }); 148 | 149 | done(); 150 | }); 151 | 152 | it('Test getters helpers: getActualKeysForTable', (done) => { 153 | const gtfs = new Gtfs(); 154 | 155 | expect(gtfs.getSchema().keysByTableName.stops).to.deep.equal(gtfs.getActualKeysForTable('stops')); 156 | 157 | const funkyStop = {}; 158 | funkyStop.route_funky_name = 'Tshboom tshboom'; 159 | funkyStop.route_esoteric_float = 120.37; 160 | gtfs.addStop(funkyStop); 161 | 162 | const standardRouteKeys = gtfs.getSchema().keysByTableName.stops; 163 | const actualRouteKeys = gtfs.getActualKeysForTable('stops'); 164 | 165 | expect(standardRouteKeys).to.not.deep.equal(actualRouteKeys); 166 | 167 | expect([...standardRouteKeys, 'route_funky_name', 'route_esoteric_float']).to.deep.equal(actualRouteKeys); 168 | 169 | done(); 170 | }); 171 | 172 | it('Tests clone and toJSON', (done) => { 173 | const path = `${__dirname}/samples/1/`; 174 | const gtfs = new Gtfs(path); 175 | 176 | const stop = gtfs.getStopWithId('stop_0'); 177 | 178 | expect(stop.toJSON()).to.equal('{"stop_id":"stop_0","stop_code":"SC0","stop_name":"Stop 0"' + 179 | ',"stop_desc":"Some stop","stop_lat":"37.728631","stop_lon":"-122.431282"}'); 180 | 181 | stop.temp = {}; 182 | expect(stop.toJSON()).to.equal('{"stop_id":"stop_0","stop_code":"SC0","stop_name":"Stop 0",' + 183 | '"stop_desc":"Some stop","stop_lat":"37.728631","stop_lon":"-122.431282","temp":{}}'); 184 | 185 | expect(stop.stop_name).to.equal('Stop 0'); 186 | stop.temp.test = 'test'; 187 | expect(stop.temp.test).to.equal('test'); 188 | const clone = stop.clone(); 189 | clone.stop_name = 'Some other stop'; 190 | 191 | expect(stop.stop_name).to.equal('Stop 0'); 192 | expect(clone.stop_name).to.equal('Some other stop'); 193 | expect(clone.temp.test).to.equal('test'); 194 | 195 | done(); 196 | }); 197 | 198 | it('Tests create gtfs object', (done) => { 199 | const path = `${__dirname}/samples/1/`; 200 | const gtfs = new Gtfs(path); 201 | 202 | const emptyCalendar = { 203 | monday: '0', tuesday: '0', wednesday: '0', thursday: '0', friday: '0', saturday: '0', sunday: '0', 204 | }; 205 | 206 | const gtfsEmptyCal = gtfs.createGtfsObjectFromSimpleObject(emptyCalendar); 207 | expect(gtfsEmptyCal.monday).to.equal(emptyCalendar.monday); 208 | expect(gtfsEmptyCal.tuesday).to.equal(emptyCalendar.tuesday); 209 | expect(gtfsEmptyCal.wednesday).to.equal(emptyCalendar.wednesday); 210 | expect(gtfsEmptyCal.thursday).to.equal(emptyCalendar.thursday); 211 | expect(gtfsEmptyCal.friday).to.equal(emptyCalendar.friday); 212 | 213 | done(); 214 | }); 215 | }); 216 | -------------------------------------------------------------------------------- /helpers/import.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable no-underscore-dangle */ 4 | 5 | const infoLog = require('debug')('gtfsNodeLib:i'); 6 | const fs = require('fs-extra'); 7 | const Papa = require('papaparse'); 8 | const { StringDecoder } = require('string_decoder'); 9 | 10 | const eachWithLog = require('./logging_iterator_wrapper'); 11 | 12 | /** 13 | * Import a table in the GTFS. 14 | * 15 | * @param {Gtfs} gtfs The GTFS in which to import the table. 16 | * @param {string} tableName The table of the name to import. 17 | */ 18 | function importTable(gtfs, tableName) { 19 | const indexKeys = gtfs._schema.indexKeysByTableName[tableName]; 20 | const fullPath = `${gtfs.getPath() + tableName}.txt`; 21 | 22 | if (fs.existsSync(fullPath)) { 23 | const fileContent = fs.readFileSync(fullPath); 24 | const { keys, rowsSlices } = getKeysAndRowsSlices( 25 | fileContent, 26 | gtfs._regexPatternObjectsByTableName.get(tableName), 27 | tableName 28 | ); 29 | gtfs._tables.set(tableName, processGtfsTable(gtfs, keys, rowsSlices, tableName, indexKeys)); 30 | } else { 31 | infoLog(`Empty table will be set for table ${tableName} (no input file at path ${gtfs._path}).`); 32 | 33 | gtfs._tables.set(tableName, (indexKeys.setOfItems) ? new Set() : new Map()); 34 | } 35 | 36 | if (gtfs._postImportItemFunction) { 37 | if (indexKeys.singleton) { 38 | gtfs._postImportItemFunction(gtfs.getIndexedTable(tableName)); 39 | } else { 40 | gtfs.forEachItemInTable(tableName, gtfs._postImportItemFunction); 41 | } 42 | } 43 | } 44 | 45 | /** 46 | * Private functions 47 | */ 48 | 49 | function getKeysAndRowsSlices(buffer, regexPatternObjects, tableName) { 50 | let keys; 51 | const rowsSlices = []; 52 | let rowsSlice; 53 | let position = 0; 54 | const batchLength = 5000000; // 5mb 55 | let merge; 56 | /* 57 | Use string decoder to properly decode utf8 characters. Characters not in the basic ASCII take more 58 | than one byte. 59 | If the end of the batch cuts one of those characters, then we will yield weird characters. 60 | decoder will accumulate any "lost" utf8 character at the end of the batch and accumulate it for the next 61 | iteration. 62 | */ 63 | const decoder = new StringDecoder('utf8'); 64 | const rowsSliceRegex = /(.*([\r\n])*)((.*[\r\n]*)*)/; 65 | 66 | while (position < buffer.length) { 67 | rowsSlice = decoder.write(buffer.slice(position, Math.min(buffer.length, position + batchLength))); 68 | 69 | if (regexPatternObjects) { 70 | regexPatternObjects.forEach(({ regex, pattern }) => { 71 | const modifiedRowsSlice = rowsSlice.replace(regex, pattern || ''); 72 | 73 | if (modifiedRowsSlice !== rowsSlice) { 74 | process.notices.addInfo(__filename, `Applying regex replace to table: "${tableName}". regex: "${regex}".`); 75 | rowsSlice = modifiedRowsSlice; 76 | } 77 | }); 78 | } 79 | 80 | const rowsSliceIndex = position / batchLength; 81 | 82 | if (!keys) { 83 | const [, firstRowSlice,, remainingRowsSlice] = rowsSlice.match(rowsSliceRegex); 84 | keys = firstRowSlice; 85 | rowsSlice = remainingRowsSlice; 86 | } 87 | 88 | if (merge) { 89 | const [, firstRowSlice,, remainingRowsSlice] = rowsSlice.match(rowsSliceRegex); 90 | rowsSlices[rowsSliceIndex - 1] += firstRowSlice; 91 | rowsSlice = remainingRowsSlice; 92 | } 93 | 94 | rowsSlices[rowsSliceIndex] = rowsSlice; 95 | 96 | merge = rowsSlices[rowsSlice.length] !== '\n'; 97 | position += batchLength; 98 | } 99 | 100 | return { 101 | keys, 102 | rowsSlices, 103 | }; 104 | } 105 | 106 | function processGtfsTable(gtfs, keys, rowsSlices, tableName, indexKeys) { 107 | let table = (indexKeys.setOfItems) ? new Set() : new Map(); 108 | 109 | if (!rowsSlices || rowsSlices.length === 0) { 110 | return table; 111 | } 112 | 113 | const parsedKeys = Papa.parse(keys, { delimiter: ',', skipEmptyLines: true }); 114 | const trimmedKeys = parsedKeys.data[0].map(key => key.trim().replace(/"/g, '')); 115 | checkThatKeysIncludeIndexKeys(trimmedKeys, indexKeys, tableName); 116 | 117 | const GtfsRow = createGtfsClassForKeys(trimmedKeys); 118 | let errorMessage; 119 | 120 | eachWithLog(`Importation:${tableName}`, rowsSlices, (rowsSlice) => { 121 | if (!rowsSlice || !rowsSlice.trim) { 122 | return; 123 | } 124 | 125 | rowsSlice = rowsSlice.trim(); 126 | 127 | const parsedRow = Papa.parse(`${keys}${rowsSlice}`, { delimiter: ',', skipEmptyLines: true }); 128 | 129 | if (parsedRow.errors.length) { 130 | if (!errorMessage) { 131 | errorMessage = `Invalid rows in table ${tableName}:\n`; 132 | } 133 | 134 | parsedRow.errors.forEach((error) => { 135 | errorMessage += `Line: ${error.row} 136 | Issue: ${error.message} 137 | Row: ${parsedRow.data[error.row].join(',')}`; 138 | }); 139 | } 140 | 141 | for (let i = 1; i < parsedRow.data.length; i += 1) { // we only want to add to the table the remaining rows 142 | const row = parsedRow.data[i]; 143 | const trimmedRow = row.map(value => value.trim()); 144 | if (trimmedRow !== null) { 145 | const item = new GtfsRow(trimmedRow); 146 | 147 | if (indexKeys.indexKey) { 148 | table.set(item[indexKeys.indexKey], item); 149 | } else if (indexKeys.firstIndexKey && indexKeys.secondIndexKey) { 150 | if (table.has(item[indexKeys.firstIndexKey]) === false) { 151 | table.set(item[indexKeys.firstIndexKey], new Map()); 152 | } 153 | 154 | table.get(item[indexKeys.firstIndexKey]).set(item[indexKeys.secondIndexKey], item); 155 | } else if (indexKeys.singleton) { 156 | table = item; 157 | } else if (indexKeys.setOfItems) { 158 | table.add(item); 159 | } 160 | } 161 | } 162 | }); 163 | 164 | if (errorMessage && gtfs._shouldThrow) { 165 | throw new Error(errorMessage); 166 | } 167 | 168 | return table; 169 | } 170 | 171 | function checkThatKeysIncludeIndexKeys(sortedKeys, indexKeys, tableName) { 172 | const deepness = (indexKeys.indexKey) ? 1 : 0; 173 | 174 | if (deepness === 1 && sortedKeys.includes(indexKeys.indexKey) === false && indexKeys.indexKey !== 'agency_id') { 175 | /* Field agency_id is optional in table agency.txt according to the specification. */ 176 | throw new Error( 177 | `Keys of table ${tableName} do not contain the index key: ${indexKeys.indexKey}.\n` 178 | + ` The values are: ${sortedKeys}` 179 | ); 180 | } 181 | 182 | if ( 183 | deepness === 2 184 | && (sortedKeys.includes(indexKeys.firstIndexKey) === false || sortedKeys.includes(indexKeys.secondIndexKey) === false) 185 | ) { 186 | throw new Error( 187 | `Keys of table ${tableName} do not contain the index keys: ` 188 | + `${indexKeys.firstIndexKey} and ${indexKeys.secondIndexKey}.\n` 189 | + ` The values are: ${sortedKeys}` 190 | ); 191 | } 192 | } 193 | 194 | function createGtfsClassForKeys(sortedKeys) { 195 | // eslint-disable-next-line func-names 196 | const GtfsRow = function GtfsRowConstructor(arrayOfValues) { 197 | // Use one letter far to use less memory 198 | // Since this object will be repeated millions of time 199 | Object.defineProperty(this, 'v', { 200 | value: arrayOfValues, 201 | enumerable: false, // Enumerable false allow us to now show 'v' in enum of item 202 | }); 203 | }; 204 | 205 | // eslint-disable-next-line func-names 206 | GtfsRow.prototype.clone = function () { 207 | const newRow = new GtfsRow(JSON.parse(JSON.stringify(this.v))); 208 | 209 | for (const key of Object.keys(this)) { 210 | newRow[key] = JSON.parse(JSON.stringify(this[key])); 211 | } 212 | 213 | return newRow; 214 | }; 215 | 216 | // eslint-disable-next-line func-names 217 | GtfsRow.prototype.toSimpleObject = function () { 218 | const jsonObj = {}; 219 | 220 | // eslint-disable-next-line 221 | for (const key in Object.getPrototypeOf(this)) { 222 | // noinspection JSUnfilteredForInLoop 223 | const value = this[key]; 224 | if (typeof value !== 'function') { 225 | jsonObj[key] = this[key]; 226 | } 227 | } 228 | 229 | for (const key of Object.keys(this)) { 230 | jsonObj[key] = this[key]; 231 | } 232 | 233 | return jsonObj; 234 | }; 235 | 236 | // eslint-disable-next-line func-names 237 | GtfsRow.prototype.toJSON = function () { 238 | return JSON.stringify(this.toSimpleObject()); 239 | }; 240 | 241 | for (const [index, key] of sortedKeys.entries()) { 242 | Object.defineProperty(GtfsRow.prototype, key, { 243 | get() { 244 | return this.v[index]; 245 | }, 246 | set(newValue) { 247 | this.v[index] = newValue; 248 | }, 249 | configurable: true, 250 | enumerable: true, 251 | }); 252 | } 253 | 254 | return GtfsRow; 255 | } 256 | 257 | function createGtfsObjectFromSimpleObject(obj) { 258 | const GtfsRow = createGtfsClassForKeys(Object.keys(obj)); 259 | return new GtfsRow(Object.values(obj)); 260 | } 261 | 262 | module.exports = { 263 | createGtfsObjectFromSimpleObject, 264 | importTable, 265 | }; 266 | -------------------------------------------------------------------------------- /helpers/getters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable no-underscore-dangle */ 4 | 5 | /** 6 | * Build the list of the keys used in a table of the GTFS. Since the GTFS specification allows any additional field, 7 | * this function allows to explore those additional values. 8 | * 9 | * @param {Gtfs} gtfs GTFS containing the table. 10 | * @param {string} tableName Table of the GTFS of which we want to key. 11 | * @return {Array.} Keys used by the items of the table. 12 | */ 13 | function getActualKeysForTable(gtfs, tableName) { 14 | const getSample = iterable => ((iterable && iterable.values().next()) ? iterable.values().next().value : undefined); 15 | const keys = [...gtfs._schema.keysByTableName[tableName]]; 16 | const deepness = gtfs._schema.deepnessByTableName[tableName]; 17 | const table = gtfs.getIndexedTable(tableName); 18 | let sampleItem; 19 | 20 | if (deepness === 0) { 21 | sampleItem = table; 22 | } else if (deepness === 1) { 23 | sampleItem = getSample(table); 24 | } else if (deepness === 2) { 25 | sampleItem = getSample(getSample(table)); 26 | } 27 | 28 | if (sampleItem && !(sampleItem instanceof Set || sampleItem instanceof Map)) { 29 | Object.keys(sampleItem.toSimpleObject()).forEach((key) => { 30 | if (gtfs._schema.keysByTableName[tableName].includes(key) === false) { 31 | keys.push(key); 32 | } 33 | }); 34 | } 35 | 36 | if (keys.length === 0) { 37 | throw new Error(`No keys found for table ${tableName}`); 38 | } 39 | 40 | return keys; 41 | } 42 | 43 | /** 44 | * Get the grand parent item using one of its child. 45 | * 46 | * @param {Object} itemWithForeignIndexId The child item. 47 | * @param {string} parentTableName The name of the table containing the parent item. 48 | * @param {string} grandParentTableName The name of the table containing the grand parent item. 49 | * @param {Gtfs} gtfs The GTFS containing the parent item 50 | * @return {Object} The grand parent item. 51 | */ 52 | function getGrandParentItem(itemWithForeignIndexId, parentTableName, grandParentTableName, gtfs) { 53 | if ( 54 | itemWithForeignIndexId === undefined || 55 | itemWithForeignIndexId === null || 56 | typeof itemWithForeignIndexId !== 'object' 57 | ) { 58 | throw new Error(`itemWithForeignIndexId must be a plain object, instead of an "${typeof itemWithForeignIndexId}"`); 59 | } 60 | if (gtfs._schema.tableNames.includes(parentTableName) === false) { 61 | throw new Error(`Cannot find table with name "${parentTableName}"`); 62 | } 63 | if (gtfs._schema.tableNames.includes(grandParentTableName) === false) { 64 | throw new Error(`Cannot find table with name "${grandParentTableName}"`); 65 | } 66 | 67 | /* Reach parent item */ 68 | const parentIndexKey = gtfs._schema.indexKeysByTableName[parentTableName].indexKey; 69 | 70 | if (itemWithForeignIndexId[parentIndexKey] === undefined) { 71 | throw new Error(`itemWithForeignIndexId should contain the foreign index key "${parentIndexKey}"`); 72 | } 73 | 74 | const parentItem = gtfs.getItemWithIndexInTable(itemWithForeignIndexId[parentIndexKey], parentTableName); 75 | 76 | if (!parentItem) { 77 | return null; 78 | } 79 | 80 | /* Reach grandparent item */ 81 | const grandParentIndexKey = gtfs._schema.indexKeysByTableName[grandParentTableName].indexKey; 82 | 83 | if (!parentItem[grandParentIndexKey]) { 84 | throw new Error(`parentItem should contain the foreign index key "${grandParentIndexKey}"${parentItem}`); 85 | } 86 | 87 | return gtfs.getItemWithIndexInTable(parentItem[grandParentIndexKey], grandParentTableName); 88 | } 89 | 90 | /** 91 | * Get the child items of an item. 92 | * 93 | * @param {Object} parentItem The parent item. 94 | * @param {string} tableName The name of the table containing the child items. 95 | * @param {Gtfs} gtfs The GTFS containing the child items. 96 | * @return {Map.} Indexed child items. 97 | */ 98 | function getIndexedItemsWithParent(parentItem, tableName, gtfs) { 99 | if (gtfs._schema.deepnessByTableName[tableName] !== 2) { 100 | throw new Error(`Table "${tableName}" is not of deepness 2.`); 101 | } 102 | if (parentItem === undefined || parentItem === null || typeof parentItem !== 'object') { 103 | throw new Error(`Parent item should be a plain object, instead of an "${typeof parentItem}"`); 104 | } 105 | 106 | const firstIndexKey = gtfs._schema.indexKeysByTableName[tableName].firstIndexKey; 107 | 108 | if (parentItem[firstIndexKey] === undefined) { 109 | throw new Error(`Parent item should contain the foreign index key "${firstIndexKey}"`); 110 | } 111 | 112 | const indexedTable = gtfs.getIndexedTable(tableName); 113 | 114 | return indexedTable.get(parentItem[firstIndexKey]); 115 | } 116 | 117 | /** 118 | * Get the child items of an item using its index. 119 | * 120 | * @param {Object} parentIndex The parent item's index. 121 | * @param {string} tableName The name of the table containing the child items. 122 | * @param {Gtfs} gtfs The GTFS containing the child items. 123 | * @return {Map.} Indexed child items. 124 | */ 125 | function getIndexedItemsWithParentIndex(parentIndex, tableName, gtfs) { 126 | if (gtfs._schema.deepnessByTableName[tableName] !== 2) { 127 | throw new Error(`Table "${tableName}" is not of deepness 2.`); 128 | } 129 | if (typeof parentIndex !== 'string') { 130 | throw new Error(`Parent item index should be a string, instead of an "${typeof parentIndex}"`); 131 | } 132 | 133 | const indexedTable = gtfs.getIndexedTable(tableName); 134 | 135 | return indexedTable.get(parentIndex); 136 | } 137 | 138 | /** 139 | * Get the item of a table using its index. 140 | * 141 | * WARNING: Will work only for the tables in which such unique indexing value exists (see schema.js for an 142 | * exhaustive list) 143 | * 144 | * @param {string} index Index of the item 145 | * @param {string} tableName Name of the table 146 | * @param {Gtfs} gtfs Gtfs object 147 | */ 148 | function getItemWithIndex(index, tableName, gtfs) { 149 | if (gtfs._schema.deepnessByTableName[tableName] !== 1) { 150 | throw new Error(`Cannot access item with only one index in "${tableName}", since the deepness is not 1.`); 151 | } 152 | if (typeof index !== 'string') { 153 | throw new Error(`Index should be a string, instead of an "${typeof index}": ${JSON.stringify(index)}`); 154 | } 155 | 156 | const indexedTable = gtfs.getIndexedTable(tableName); 157 | 158 | return indexedTable.get(index); 159 | } 160 | 161 | /** 162 | * Get the item of a table using its indexes. 163 | * 164 | * WARNING: Will work only for the tables which have a double level of indexing, which is required when there is no 165 | * value uniquely identifying each item (see schema.js for an exhaustive list) 166 | * 167 | * @param {string} firstIndex First index of the item 168 | * @param {string} secondIndex Second index of the item 169 | * @param {string} tableName Name of the table 170 | * @param {Gtfs} gtfs Gtfs object 171 | */ 172 | function getItemWithIndexes(firstIndex, secondIndex, tableName, gtfs) { 173 | if (gtfs._schema.deepnessByTableName[tableName] !== 2) { 174 | throw new Error(`Cannot access item with two indexes in "${tableName}", since the deep is not 2.`); 175 | } 176 | if (firstIndex === undefined || firstIndex === null || typeof firstIndex !== 'string') { 177 | throw new Error(`First index should be a string, instead of an "${typeof firstIndex}"`); 178 | } 179 | if (secondIndex === undefined || secondIndex === null || typeof secondIndex !== 'string') { 180 | throw new Error(`Second index should be a string, instead of an "${typeof secondIndex}"`); 181 | } 182 | 183 | const indexedTable = gtfs.getIndexedTable(tableName); 184 | 185 | return (indexedTable.has(firstIndex)) ? indexedTable.get(firstIndex).get(secondIndex) : null; 186 | } 187 | 188 | /** 189 | * Get the parent item using one of its child. 190 | * 191 | * @param {Object} itemWithForeignIndexId The child item. 192 | * @param {string} tableName The name of the table containing the parent item. 193 | * @param {Gtfs} gtfs The GTFS containing the parent item 194 | * @return {Object} The parent item. 195 | */ 196 | function getParentItem(itemWithForeignIndexId, tableName, gtfs) { 197 | if ( 198 | itemWithForeignIndexId === undefined || 199 | itemWithForeignIndexId === null || 200 | typeof itemWithForeignIndexId !== 'object' 201 | ) { 202 | throw new Error(`itemWithForeignIndexId must be a plain object, instead of an "${typeof itemWithForeignIndexId}"`); 203 | } 204 | 205 | const indexKey = gtfs._schema.indexKeysByTableName[tableName].indexKey; 206 | 207 | if (itemWithForeignIndexId[indexKey] === undefined) { 208 | throw new Error( 209 | `itemWithForeignIndexId should contain the foreign index key "${indexKey}", ` + 210 | `but is: ${JSON.stringify(itemWithForeignIndexId)}` 211 | ); 212 | } 213 | 214 | return gtfs.getItemWithIndexInTable(itemWithForeignIndexId[indexKey], tableName); 215 | } 216 | 217 | module.exports = { 218 | getActualKeysForTable, 219 | getGrandParentItem, 220 | getIndexedItemsWithParent, 221 | getIndexedItemsWithParentIndex, 222 | getItemWithIndex, 223 | getItemWithIndexes, 224 | getParentItem, 225 | }; 226 | -------------------------------------------------------------------------------- /tests/samples/3/stops.txt: -------------------------------------------------------------------------------- 1 | stop_id,stop_code,stop_name,stop_desc,stop_lat,stop_lon 2 | êêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêê,êêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêê,êêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêê,êêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêêê,37.728631,-122.431282 3 | --------------------------------------------------------------------------------