├── .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 |
5 |
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 |
4 |
5 |
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 |
--------------------------------------------------------------------------------