├── .babelrc ├── .browserslistrc ├── .circleci └── config.yml ├── .editorconfig ├── .eslintrc.js ├── .firebaserc ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc.js ├── CODEOWNERS ├── DOC_INDEX.md ├── LICENSE.md ├── README.md ├── codegen.yml ├── firebase.json ├── package-lock.json ├── package.json ├── schemas ├── journeyplanner2.json ├── mobility.json └── nsr.json ├── scripts ├── fetch-schemas.ts ├── generate-types.sh ├── prepublish.sh └── validate-queries.js ├── src ├── api.ts ├── bikeRental │ ├── index.ts │ └── query.ts ├── client.ts ├── config.ts ├── constants │ ├── featureCategory.ts │ └── rateLimits.ts ├── departure │ ├── index.ts │ ├── mapper.ts │ └── query.ts ├── fetch.browser.ts ├── fetch.ts ├── fields │ ├── Authority.ts │ ├── BikeRentalStation.ts │ ├── BookingArrangement.ts │ ├── Departure.ts │ ├── EstimatedCall.ts │ ├── Interchange.ts │ ├── Leg.ts │ ├── Line.ts │ ├── Notice.ts │ ├── Operator.ts │ ├── Place.ts │ ├── PointsOnLink.ts │ ├── Quay.ts │ ├── ServiceJourney.ts │ ├── Situation.ts │ └── StopPlace.ts ├── geocoder │ ├── autocomplete │ │ └── index.ts │ ├── helper.ts │ ├── index.ts │ ├── reverse │ │ └── index.ts │ └── types.ts ├── geocoderLegacy │ ├── countyIds.ts │ └── index.ts ├── http.ts ├── index.ts ├── journeyPlanner │ ├── .eslintrc.js │ └── types.ts ├── mobility │ ├── getOperators │ │ ├── index.ts │ │ └── query.ts │ ├── getStations │ │ ├── index.ts │ │ └── query.ts │ ├── getStationsById │ │ ├── index.ts │ │ └── query.ts │ ├── getVehicles │ │ ├── index.ts │ │ └── query.ts │ ├── index.ts │ └── types.ts ├── nearest │ ├── index.ts │ ├── query.ts │ └── types.ts ├── nsr │ ├── getFareZone │ │ └── index.ts │ ├── getGroupOfStopPlaces │ │ └── index.ts │ ├── getParking │ │ └── index.ts │ ├── getParkingsForStopPlace │ │ └── index.ts │ ├── getQuay │ │ └── index.ts │ ├── getStopPlace │ │ └── index.ts │ ├── getStopPlaceForQuay │ │ └── index.ts │ ├── getTariffZone │ │ └── index.ts │ ├── getTopographicPlace │ │ └── index.ts │ ├── index.ts │ └── types.ts ├── stopPlace │ ├── index.ts │ └── query.ts ├── trip │ ├── index.ts │ ├── mapper.ts │ └── query.ts ├── types │ ├── Coordinates.ts │ ├── DestinationDisplay.ts │ ├── Feature.ts │ ├── FlexibleLineType.ts │ ├── Location.ts │ ├── Mode.ts │ ├── MultilingualString.ts │ └── StopPlace.ts └── utils.ts ├── test.js ├── tsconfig.json └── tsconfig.typedoc.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-typescript"] 3 | } 4 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | last 2 version 2 | maintained node versions 3 | ie 11 4 | > 1% in NO 5 | not dead 6 | firefox >= 45 7 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | orbs: 3 | node: 'circleci/node@5.0.0' 4 | workflows: 5 | build-and-test: 6 | jobs: 7 | - node/test: 8 | version: 14.18.3 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | charset = utf-8 9 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | env: { 5 | node: true, 6 | }, 7 | plugins: ['@typescript-eslint', 'eslint-plugin-tsdoc'], 8 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], 9 | rules: { 10 | '@typescript-eslint/array-type': ['error', { default: 'array-simple' }], 11 | '@typescript-eslint/member-delimiter-style': 'off', 12 | 'tsdoc/syntax': 'error', 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "entur-sdk" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | docs 4 | .DS_Store 5 | .idea/ 6 | .tern-port 7 | .firebase/ 8 | *.tgz 9 | *.log 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | docs 2 | lib 3 | schemas 4 | .docz 5 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'all', 3 | tabWidth: 4, 4 | semi: false, 5 | singleQuote: true, 6 | } 7 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @entur/team-selvbetjent 2 | -------------------------------------------------------------------------------- /DOC_INDEX.md: -------------------------------------------------------------------------------- 1 | # Entur SDK - Deprecated January 2023 2 | 3 | Given the low usage of the SDK, we have decided to deprecate this library. If you wish to perform travel searches or queries on our mobility API, we recommend using GraphQL directly. 4 | 5 | https://developer.entur.org/pages-journeyplanner-journeyplanner 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # EUROPEAN UNION PUBLIC LICENCE v. 1.2 2 | 3 | EUPL © the European Union 2007, 2016 4 | 5 | This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined below) which is provided under the 6 | terms of this Licence. Any use of the Work, other than as authorised under this Licence is prohibited (to the extent such 7 | use is covered by a right of the copyright holder of the Work). 8 | The Work is provided under the terms of this Licence when the Licensor (as defined below) has placed the following 9 | notice immediately following the copyright notice for the Work: 10 | Licensed under the EUPL 11 | or has expressed by any other means his willingness to license under the EUPL. 12 | 13 | ## 1.Definitions 14 | 15 | In this Licence, the following terms have the following meaning: 16 | 17 | - ‘The Licence’:this Licence. 18 | - ‘The Original Work’:the work or software distributed or communicated by the Licensor under this Licence, available 19 | as Source Code and also as Executable Code as the case may be. 20 | - ‘Derivative Works’:the works or software that could be created by the Licensee, based upon the Original Work or 21 | modifications thereof. This Licence does not define the extent of modification or dependence on the Original Work 22 | required in order to classify a work as a Derivative Work; this extent is determined by copyright law applicable in 23 | the country mentioned in Article 15. 24 | - ‘The Work’:the Original Work or its Derivative Works. 25 | - ‘The Source Code’:the human-readable form of the Work which is the most convenient for people to study and 26 | modify. 27 | - ‘The Executable Code’:any code which has generally been compiled and which is meant to be interpreted by 28 | a computer as a program. 29 | - ‘The Licensor’:the natural or legal person that distributes or communicates the Work under the Licence. 30 | - ‘Contributor(s)’:any natural or legal person who modifies the Work under the Licence, or otherwise contributes to 31 | the creation of a Derivative Work. 32 | - ‘The Licensee’ or ‘You’:any natural or legal person who makes any usage of the Work under the terms of the 33 | Licence. 34 | - ‘Distribution’ or ‘Communication’:any act of selling, giving, lending, renting, distributing, communicating, 35 | transmitting, or otherwise making available, online or offline, copies of the Work or providing access to its essential 36 | functionalities at the disposal of any other natural or legal person. 37 | 38 | ## 2.Scope of the rights granted by the Licence 39 | 40 | The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, sublicensable licence to do the following, for 41 | the duration of copyright vested in the Original Work: 42 | 43 | - use the Work in any circumstance and for all usage, 44 | - reproduce the Work, 45 | - modify the Work, and make Derivative Works based upon the Work, 46 | - communicate to the public, including the right to make available or display the Work or copies thereof to the public 47 | and perform publicly, as the case may be, the Work, 48 | - distribute the Work or copies thereof, 49 | - lend and rent the Work or copies thereof, 50 | - sublicense rights in the Work or copies thereof. 51 | Those rights can be exercised on any media, supports and formats, whether now known or later invented, as far as the 52 | applicable law permits so. 53 | In the countries where moral rights apply, the Licensor waives his right to exercise his moral right to the extent allowed 54 | by law in order to make effective the licence of the economic rights here above listed. 55 | The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to any patents held by the Licensor, to the 56 | extent necessary to make use of the rights granted on the Work under this Licence. 57 | 58 | 3.Communication of the Source Code 59 | The Licensor may provide the Work either in its Source Code form, or as Executable Code. If the Work is provided as 60 | Executable Code, the Licensor provides in addition a machine-readable copy of the Source Code of the Work along with 61 | each copy of the Work that the Licensor distributes or indicates, in a notice following the copyright notice attached to 62 | the Work, a repository where the Source Code is easily and freely accessible for as long as the Licensor continues to 63 | distribute or communicate the Work. 64 | 65 | ## 4.Limitations on copyright 66 | 67 | Nothing in this Licence is intended to deprive the Licensee of the benefits from any exception or limitation to the 68 | exclusive rights of the rights owners in the Work, of the exhaustion of those rights or of other applicable limitations 69 | thereto. 70 | 71 | ## 5.Obligations of the Licensee 72 | 73 | The grant of the rights mentioned above is subject to some restrictions and obligations imposed on the Licensee. Those 74 | obligations are the following: 75 | 76 | Attribution right: The Licensee shall keep intact all copyright, patent or trademarks notices and all notices that refer to 77 | the Licence and to the disclaimer of warranties. The Licensee must include a copy of such notices and a copy of the 78 | Licence with every copy of the Work he/she distributes or communicates. The Licensee must cause any Derivative Work 79 | to carry prominent notices stating that the Work has been modified and the date of modification. 80 | 81 | Copyleft clause: If the Licensee distributes or communicates copies of the Original Works or Derivative Works, this 82 | Distribution or Communication will be done under the terms of this Licence or of a later version of this Licence unless 83 | the Original Work is expressly distributed only under this version of the Licence — for example by communicating 84 | ‘EUPL v. 1.2 only’. The Licensee (becoming Licensor) cannot offer or impose any additional terms or conditions on the 85 | Work or Derivative Work that alter or restrict the terms of the Licence. 86 | 87 | Compatibility clause: If the Licensee Distributes or Communicates Derivative Works or copies thereof based upon both 88 | the Work and another work licensed under a Compatible Licence, this Distribution or Communication can be done 89 | under the terms of this Compatible Licence. For the sake of this clause, ‘Compatible Licence’ refers to the licences listed 90 | in the appendix attached to this Licence. Should the Licensee's obligations under the Compatible Licence conflict with 91 | his/her obligations under this Licence, the obligations of the Compatible Licence shall prevail. 92 | 93 | Provision of Source Code: When distributing or communicating copies of the Work, the Licensee will provide 94 | a machine-readable copy of the Source Code or indicate a repository where this Source will be easily and freely available 95 | for as long as the Licensee continues to distribute or communicate the Work. 96 | Legal Protection: This Licence does not grant permission to use the trade names, trademarks, service marks, or names 97 | of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and 98 | reproducing the content of the copyright notice. 99 | 100 | ## 6.Chain of Authorship 101 | 102 | The original Licensor warrants that the copyright in the Original Work granted hereunder is owned by him/her or 103 | licensed to him/her and that he/she has the power and authority to grant the Licence. 104 | Each Contributor warrants that the copyright in the modifications he/she brings to the Work are owned by him/her or 105 | licensed to him/her and that he/she has the power and authority to grant the Licence. 106 | Each time You accept the Licence, the original Licensor and subsequent Contributors grant You a licence to their contributions 107 | to the Work, under the terms of this Licence. 108 | 109 | ## 7.Disclaimer of Warranty 110 | 111 | The Work is a work in progress, which is continuously improved by numerous Contributors. It is not a finished work 112 | and may therefore contain defects or ‘bugs’ inherent to this type of development. 113 | For the above reason, the Work is provided under the Licence on an ‘as is’ basis and without warranties of any kind 114 | concerning the Work, including without limitation merchantability, fitness for a particular purpose, absence of defects or 115 | errors, accuracy, non-infringement of intellectual property rights other than copyright as stated in Article 6 of this 116 | Licence. 117 | This disclaimer of warranty is an essential part of the Licence and a condition for the grant of any rights to the Work. 118 | 119 | ## 8.Disclaimer of Liability 120 | 121 | Except in the cases of wilful misconduct or damages directly caused to natural persons, the Licensor will in no event be 122 | liable for any direct or indirect, material or moral, damages of any kind, arising out of the Licence or of the use of the 123 | Work, including without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, loss 124 | of data or any commercial damage, even if the Licensor has been advised of the possibility of such damage. However, 125 | the Licensor will be liable under statutory product liability laws as far such laws apply to the Work. 126 | 127 | ## 9.Additional agreements 128 | 129 | While distributing the Work, You may choose to conclude an additional agreement, defining obligations or services 130 | consistent with this Licence. However, if accepting obligations, You may act only on your own behalf and on your sole 131 | responsibility, not on behalf of the original Licensor or any other Contributor, and only if You agree to indemnify, 132 | defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against such Contributor by 133 | the fact You have accepted any warranty or additional liability. 134 | 135 | ## 10.Acceptance of the Licence 136 | 137 | The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ placed under the bottom of a window 138 | displaying the text of this Licence or by affirming consent in any other similar way, in accordance with the rules of 139 | applicable law. Clicking on that icon indicates your clear and irrevocable acceptance of this Licence and all of its terms 140 | and conditions. 141 | Similarly, you irrevocably accept this Licence and all of its terms and conditions by exercising any rights granted to You 142 | by Article 2 of this Licence, such as the use of the Work, the creation by You of a Derivative Work or the Distribution 143 | or Communication by You of the Work or copies thereof. 144 | 145 | ## 11.Information to the public 146 | 147 | In case of any Distribution or Communication of the Work by means of electronic communication by You (for example, 148 | by offering to download the Work from a remote location) the distribution channel or media (for example, a website) 149 | must at least provide to the public the information requested by the applicable law regarding the Licensor, the Licence 150 | and the way it may be accessible, concluded, stored and reproduced by the Licensee. 151 | 152 | ## 12.Termination of the Licence 153 | 154 | The Licence and the rights granted hereunder will terminate automatically upon any breach by the Licensee of the terms 155 | of the Licence. 156 | Such a termination will not terminate the licences of any person who has received the Work from the Licensee under 157 | the Licence, provided such persons remain in full compliance with the Licence. 158 | 159 | ## 13.Miscellaneous 160 | 161 | Without prejudice of Article 9 above, the Licence represents the complete agreement between the Parties as to the 162 | Work. 163 | If any provision of the Licence is invalid or unenforceable under applicable law, this will not affect the validity or 164 | enforceability of the Licence as a whole. Such provision will be construed or reformed so as necessary to make it valid 165 | and enforceable. 166 | The European Commission may publish other linguistic versions or new versions of this Licence or updated versions of 167 | the Appendix, so far this is required and reasonable, without reducing the scope of the rights granted by the Licence. 168 | New versions of the Licence will be published with a unique version number. 169 | All linguistic versions of this Licence, approved by the European Commission, have identical value. Parties can take 170 | advantage of the linguistic version of their choice. 171 | 172 | ## 14.Jurisdiction 173 | 174 | Without prejudice to specific agreement between parties, 175 | 176 | - any litigation resulting from the interpretation of this License, arising between the European Union institutions, 177 | bodies, offices or agencies, as a Licensor, and any Licensee, will be subject to the jurisdiction of the Court of Justice 178 | of the European Union, as laid down in article 272 of the Treaty on the Functioning of the European Union, 179 | - any litigation arising between other parties and resulting from the interpretation of this License, will be subject to 180 | the exclusive jurisdiction of the competent court where the Licensor resides or conducts its primary business. 181 | 182 | ## 15.Applicable Law 183 | 184 | Without prejudice to specific agreement between parties, 185 | 186 | - this Licence shall be governed by the law of the European Union Member State where the Licensor has his seat, 187 | resides or has his registered office, 188 | - this licence shall be governed by Belgian law if the Licensor has no seat, residence or registered office inside 189 | a European Union Member State. 190 | 191 | Appendix 192 | 193 | ‘Compatible Licences’ according to Article 5 EUPL are: 194 | 195 | - GNU General Public License (GPL) v. 2, v. 3 196 | - GNU Affero General Public License (AGPL) v. 3 197 | - Open Software License (OSL) v. 2.1, v. 3.0 198 | - Eclipse Public License (EPL) v. 1.0 199 | - CeCILL v. 2.0, v. 2.1 200 | - Mozilla Public Licence (MPL) v. 2 201 | - GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 202 | - Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for works other than software 203 | - European Union Public Licence (EUPL) v. 1.1, v. 1.2 204 | - Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong Reciprocity (LiLiQ-R+). 205 | 206 | The European Commission may update this Appendix to later versions of the above licences without producing 207 | a new version of the EUPL, as long as they provide the rights granted in Article 2 of this Licence and protect the 208 | covered Source Code from exclusive appropriation. 209 | All other changes or additions to this Appendix require the production of a new EUPL version. 210 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Entur SDK - Deprecated January 2023 2 | 3 | Given the low usage of the SDK, we have decided to deprecate this library. If you wish to perform travel searches or queries on our mobility API, we recommend using GraphQL directly. 4 | 5 | https://developer.entur.org/pages-journeyplanner-journeyplanner 6 | -------------------------------------------------------------------------------- /codegen.yml: -------------------------------------------------------------------------------- 1 | overwrite: true 2 | schema: 'https://api.entur.io/journey-planner/v3/graphql' 3 | hooks: 4 | afterAllFileWrite: 5 | - prettier --write 6 | generates: 7 | src/journeyPlanner/types.ts: 8 | plugins: 9 | - 'typescript' 10 | config: 11 | strictScalars: true 12 | avoidOptionals: 13 | field: true 14 | inputValue: true 15 | object: false 16 | defaultValue: false 17 | scalars: 18 | Coordinates: 'Array<[lat: number, lon: number]>' 19 | Date: string 20 | DateTime: string 21 | DoubleFunction: string 22 | LocalTime: string 23 | Long: number 24 | Time: string 25 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "docs", 4 | "ignore": ["firebase.json", "**/.*", "**/node_modules/**"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@entur/sdk", 3 | "version": "6.0.0", 4 | "license": "EUPL-1.2", 5 | "main": "./lib/index.js", 6 | "bugs": { 7 | "url": "https://github.com/entur/sdk/issues" 8 | }, 9 | "browser": { 10 | "./lib/fetch.js": "./lib/fetch.browser.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/entur/sdk.git" 15 | }, 16 | "typings": "./lib/index.d.ts", 17 | "files": [ 18 | "lib", 19 | "index.d.ts" 20 | ], 21 | "scripts": { 22 | "lint": "eslint src/ scripts/ --ext=js,ts && prettier . --check", 23 | "format": "prettier . --write", 24 | "test": "npm run lint && ts-node scripts/validate-queries.js && tsc --noEmit --emitDeclarationOnly false -p .", 25 | "prebuild": "npm test && rm -rf lib", 26 | "declaration": "tsc", 27 | "build": "babel --extensions .ts src --out-dir lib && npm run declaration", 28 | "prepublishOnly": "npm run build", 29 | "docs": "npm run build:docs -- --watch", 30 | "build:docs": "typedoc --out docs src/index.ts --tsconfig ./tsconfig.typedoc.json --readme ./DOC_INDEX.md", 31 | "deploy:docs": "npm run build:docs && firebase deploy --only hosting", 32 | "generate-types": "./scripts/generate-types.sh", 33 | "fetch-schemas": "ts-node scripts/fetch-schemas.ts" 34 | }, 35 | "dependencies": { 36 | "@babel/polyfill": "^7.12.1", 37 | "@turf/bbox": "^6.3.0", 38 | "@turf/destination": "^6.3.0", 39 | "@turf/helpers": "^6.3.0", 40 | "@types/geojson": "^7946.0.7", 41 | "@types/node-fetch": "^2.5.10", 42 | "clean-deep": "^3.4.0", 43 | "node-fetch": "^2.6.7", 44 | "promise-throttle": "^1.1.2", 45 | "qs": "^6.10.1", 46 | "regenerator-runtime": "^0.13.8" 47 | }, 48 | "devDependencies": { 49 | "@babel/cli": "^7.14.3", 50 | "@babel/core": "^7.14.3", 51 | "@babel/node": "^7.14.7", 52 | "@babel/preset-env": "^7.14.7", 53 | "@babel/preset-typescript": "^7.13.0", 54 | "@babel/register": "^7.13.16", 55 | "@graphql-codegen/cli": "^2.3.1", 56 | "@graphql-codegen/typescript": "^2.4.2", 57 | "@types/qs": "^6.9.6", 58 | "@typescript-eslint/eslint-plugin": "^5.9.1", 59 | "@typescript-eslint/parser": "^5.9.1", 60 | "babel-eslint": "^10.1.0", 61 | "eslint": "^8.6.0", 62 | "eslint-plugin-import": "^2.23.2", 63 | "eslint-plugin-tsdoc": "^0.2.14", 64 | "firebase-tools": "^10.2.1", 65 | "graphql": "^16.3.0", 66 | "prettier": "^2.6.0", 67 | "swagger-typescript-api": "^9.1.2", 68 | "ts-node": "^10.0.0", 69 | "typedoc": "^0.22.3", 70 | "typescript": "^4.4.3" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /scripts/fetch-schemas.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import fetch from 'node-fetch' 3 | 4 | import { getIntrospectionQuery } from 'graphql' 5 | 6 | const { writeFile } = fs.promises 7 | 8 | function runIntrospectionQuery(url: string): Promise { 9 | return fetch(url, { 10 | method: 'POST', 11 | headers: { 12 | 'Content-Type': 'application/json', 13 | 'ET-Client-Name': 'entur-sdk', 14 | }, 15 | body: JSON.stringify({ 16 | query: getIntrospectionQuery(), 17 | operationName: 'IntrospectionQuery', 18 | }), 19 | }).then((res) => res.json()) 20 | } 21 | 22 | runIntrospectionQuery('https://api.entur.io/journey-planner/v2/graphql').then( 23 | (schema) => 24 | writeFile( 25 | 'schemas/journeyplanner2.json', 26 | JSON.stringify(schema, undefined, 2), 27 | ), 28 | ) 29 | 30 | runIntrospectionQuery('https://api.entur.io/stop-places/v1/graphql').then( 31 | (schema) => 32 | writeFile('schemas/nsr.json', JSON.stringify(schema, undefined, 2)), 33 | ) 34 | 35 | runIntrospectionQuery('https://api.entur.io/mobility/v2/graphql').then( 36 | (schema) => 37 | writeFile( 38 | 'schemas/mobility.json', 39 | JSON.stringify(schema, undefined, 2), 40 | ), 41 | ) 42 | -------------------------------------------------------------------------------- /scripts/generate-types.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | swagger-typescript-api -p "https://api.entur.io/stop-places/v1/read/api-docs" -n ./src/nsr/types.ts --no-client 4 | graphql-codegen --config codegen.yml 5 | -------------------------------------------------------------------------------- /scripts/prepublish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | npm version prerelease 4 | git push && git push --tags 5 | npm publish --tag next 6 | -------------------------------------------------------------------------------- /scripts/validate-queries.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | /* 4 | This is a script that validates queries against the GraphQL schemas for JourneyPlanner and NSR. 5 | To download updated schemas, run the ./scripts/fetch-schemas.sh script. 6 | */ 7 | 8 | import { parse, buildClientSchema } from 'graphql' 9 | import { validate } from 'graphql/validation' 10 | 11 | import journeyplanner2SchemaJSON from '../schemas/journeyplanner2.json' 12 | import nsrSchemaJSON from '../schemas/nsr.json' 13 | import mobilitySchemaJSON from '../schemas/mobility.json' 14 | 15 | import { 16 | getBikeRentalStationQuery, 17 | getBikeRentalStationsQuery, 18 | getBikeRentalStationsByPositionQuery, 19 | } from '../src/bikeRental/query' 20 | 21 | import { 22 | getDeparturesFromStopPlacesQuery, 23 | getDeparturesFromQuayQuery, 24 | getDeparturesBetweenStopPlacesQuery, 25 | getDeparturesForServiceJourneyQuery, 26 | } from '../src/departure/query' 27 | 28 | import { getNearestPlacesQuery } from '../src/nearest/query' 29 | 30 | import { 31 | getStopPlaceQuery, 32 | getStopPlacesQuery, 33 | getParentStopPlaceQuery, 34 | getStopPlacesByBboxQuery, 35 | getQuaysForStopPlaceQuery, 36 | } from '../src/stopPlace/query' 37 | 38 | import { getTripPatternQuery } from '../src/trip/query' 39 | 40 | import getOperatorsQuery from '../src/mobility/getOperators/query' 41 | import getVehiclesQuery from '../src/mobility/getVehicles/query' 42 | import getStationsQuery from '../src/mobility/getStations/query' 43 | 44 | const journeyplanner2Schema = buildClientSchema(journeyplanner2SchemaJSON.data) 45 | const nsrSchema = buildClientSchema(nsrSchemaJSON.data) 46 | const mobilitySchema = buildClientSchema(mobilitySchemaJSON.data) 47 | 48 | const jp2Queries = [ 49 | { getBikeRentalStationQuery }, 50 | { getBikeRentalStationsQuery }, 51 | { getBikeRentalStationsByPositionQuery }, 52 | { getDeparturesFromStopPlacesQuery }, 53 | { getDeparturesFromQuayQuery }, 54 | { getDeparturesBetweenStopPlacesQuery }, 55 | { getDeparturesForServiceJourneyQuery }, 56 | { getNearestPlacesQuery }, 57 | { getStopPlaceQuery }, 58 | { getStopPlacesQuery }, 59 | { getParentStopPlaceQuery }, 60 | { getStopPlacesByBboxQuery }, 61 | { getQuaysForStopPlaceQuery }, 62 | { getTripPatternQuery }, 63 | ] 64 | 65 | const mobilityQueries = [ 66 | { getOperatorsQuery }, 67 | { getVehiclesQuery }, 68 | { getStationsQuery }, 69 | ] 70 | 71 | function validateQuery(queryName, query, schema) { 72 | try { 73 | const validationResult = validate(schema, parse(query)) 74 | if (validationResult.length) { 75 | throw new Error(validationResult[0]) 76 | } 77 | } catch (error) { 78 | console.error(`Query "${queryName}" is not valid`) 79 | console.error(error) 80 | process.exit(1) 81 | } 82 | } 83 | 84 | function runValidations() { 85 | jp2Queries.forEach((obj) => { 86 | const [name, query] = Object.entries(obj)[0] 87 | validateQuery(name, query, journeyplanner2Schema) 88 | }) 89 | mobilityQueries.forEach((obj) => { 90 | const [name, query] = Object.entries(obj)[0] 91 | validateQuery(name, query, mobilitySchema) 92 | }) 93 | } 94 | 95 | runValidations() 96 | -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | import { post, RequestOptions } from './http' 2 | 3 | import { getJourneyPlannerHost, getMobilityHost, getNSRHost } from './config' 4 | import { ServiceConfig } from './config' 5 | 6 | function minify(query: string): string { 7 | return query.trim().replace(/\s+/g, ' ') 8 | } 9 | 10 | function errorHandler(response: any) { 11 | if (response?.errors?.[0]) { 12 | throw new Error(`GraphQL: ${response.errors[0].message}`) 13 | } 14 | 15 | if (!response?.data) { 16 | throw new Error('Entur SDK: No data available') 17 | } 18 | return response.data 19 | } 20 | 21 | export function getGraphqlParams( 22 | query: string, 23 | variables: Record, 24 | ): { 25 | query: string 26 | variables?: Record 27 | } { 28 | return { 29 | query: minify(query), 30 | variables, 31 | } 32 | } 33 | 34 | export function journeyPlannerQuery( 35 | queryObj: string, 36 | variables: Record, 37 | config: ServiceConfig, 38 | options?: RequestOptions, 39 | ): Promise { 40 | const { host, headers } = getJourneyPlannerHost(config) 41 | const url = `${host}/graphql` 42 | 43 | const params = getGraphqlParams(queryObj, variables) 44 | 45 | return post(url, params, headers, config.fetch, options).then(errorHandler) 46 | } 47 | 48 | export function nsrQuery( 49 | query: string, 50 | variables: Record, 51 | config: ServiceConfig, 52 | options?: RequestOptions, 53 | ): Promise { 54 | const { host, headers } = getNSRHost(config) 55 | const url = `${host}/graphql` 56 | 57 | const params = { 58 | query: minify(query), 59 | variables, 60 | } 61 | 62 | return post(url, params, headers, config.fetch, options).then(errorHandler) 63 | } 64 | 65 | export function mobilityQuery( 66 | query: string, 67 | variables: Record, 68 | config: ServiceConfig, 69 | options?: RequestOptions, 70 | ): Promise { 71 | const { host, headers } = getMobilityHost(config) 72 | const url = `${host}/graphql` 73 | 74 | const params = { 75 | query: minify(query), 76 | variables, 77 | } 78 | 79 | return post(url, params, headers, config.fetch, options).then(errorHandler) 80 | } 81 | -------------------------------------------------------------------------------- /src/bikeRental/index.ts: -------------------------------------------------------------------------------- 1 | import { RequestOptions } from '../http' 2 | import { journeyPlannerQuery } from '../api' 3 | 4 | import { 5 | getBikeRentalStationQuery, 6 | getBikeRentalStationsQuery, 7 | getBikeRentalStationsByPositionQuery, 8 | } from './query' 9 | 10 | import { convertPositionToBbox, forceOrder } from '../utils' 11 | import { Coordinates } from '../types/Coordinates' 12 | 13 | import { BikeRentalStation } from '../fields/BikeRentalStation' 14 | import { getServiceConfig, ArgumentConfig } from '../config' 15 | 16 | export function createGetBikeRentalStation(argConfig: ArgumentConfig) { 17 | const config = getServiceConfig(argConfig) 18 | 19 | /** 20 | * @param stationId - The ID of the bike rental station in question 21 | * @param options - RequestOptions 22 | */ 23 | return function getBikeRentalStation( 24 | stationId: string, 25 | options?: RequestOptions, 26 | ): Promise { 27 | const variables = { 28 | id: stationId, 29 | } 30 | 31 | return journeyPlannerQuery<{ bikeRentalStation: BikeRentalStation }>( 32 | getBikeRentalStationQuery, 33 | variables, 34 | config, 35 | options, 36 | ).then((data) => data?.bikeRentalStation) 37 | } 38 | } 39 | 40 | export function createGetBikeRentalStations(argConfig: ArgumentConfig) { 41 | const config = getServiceConfig(argConfig) 42 | 43 | return function getBikeRentalStations( 44 | stationIds: string[], 45 | options?: RequestOptions, 46 | ): Promise> { 47 | if (!stationIds || !Array.isArray(stationIds)) { 48 | throw new Error( 49 | `getBikeRentalStations takes an array of strings, but got ${typeof stationIds}`, 50 | ) 51 | } 52 | 53 | if (stationIds.length === 0) { 54 | return Promise.resolve([]) 55 | } 56 | 57 | const variables = { 58 | ids: stationIds, 59 | } 60 | 61 | return journeyPlannerQuery<{ 62 | bikeRentalStations?: BikeRentalStation[] 63 | }>(getBikeRentalStationsQuery, variables, config, options) 64 | .then((data) => data?.bikeRentalStations || []) 65 | .then((stations) => 66 | forceOrder(stations, stationIds, ({ id }) => id), 67 | ) 68 | } 69 | } 70 | 71 | export function createGetBikeRentalStationsByPosition( 72 | argConfig: ArgumentConfig, 73 | ) { 74 | const config = getServiceConfig(argConfig) 75 | 76 | return function getBikeRentalStationsByPosition( 77 | coordinates: Coordinates, 78 | distance = 500, 79 | options?: RequestOptions, 80 | ): Promise { 81 | const variables = convertPositionToBbox(coordinates, distance) 82 | 83 | return journeyPlannerQuery<{ 84 | bikeRentalStationsByBbox?: BikeRentalStation[] 85 | }>( 86 | getBikeRentalStationsByPositionQuery, 87 | variables, 88 | config, 89 | options, 90 | ).then((data) => data?.bikeRentalStationsByBbox || []) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/bikeRental/query.ts: -------------------------------------------------------------------------------- 1 | import { 2 | fragmentName as bikeRentalStationFields, 3 | fragments as bikeRentalStationFragments, 4 | } from '../fields/BikeRentalStation' 5 | 6 | export const getBikeRentalStationQuery = ` 7 | query($id: String!) { 8 | bikeRentalStation(id: $id) { 9 | ...${bikeRentalStationFields} 10 | } 11 | } 12 | 13 | ${bikeRentalStationFragments.join('')} 14 | ` 15 | 16 | export const getBikeRentalStationsQuery = ` 17 | query($ids: [String!]) { 18 | bikeRentalStations(ids: $ids) { 19 | ...${bikeRentalStationFields} 20 | } 21 | } 22 | 23 | ${bikeRentalStationFragments.join('')} 24 | ` 25 | 26 | export const getBikeRentalStationsByPositionQuery = ` 27 | query( 28 | $minLat: Float, 29 | $minLng: Float, 30 | $maxLat: Float, 31 | $maxLng: Float 32 | ) { 33 | bikeRentalStationsByBbox( 34 | minimumLatitude: $minLat, 35 | minimumLongitude: $minLng, 36 | maximumLatitude: $maxLat, 37 | maximumLongitude: $maxLng, 38 | ) { 39 | ...${bikeRentalStationFields} 40 | } 41 | } 42 | 43 | ${bikeRentalStationFragments.join('')} 44 | ` 45 | -------------------------------------------------------------------------------- /src/client.ts: -------------------------------------------------------------------------------- 1 | import { RequestOptions } from './http' 2 | import { journeyPlannerQuery, nsrQuery } from './api' 3 | 4 | import { createFindTrips, createGetTripPatterns } from './trip' 5 | 6 | import { 7 | createGetDeparturesFromStopPlace, 8 | createGetDeparturesFromStopPlaces, 9 | createGetDeparturesFromQuays, 10 | createGetDeparturesBetweenStopPlaces, 11 | createGetDeparturesForServiceJourney, 12 | getStopPlaceDeparturesDEPRECATED, 13 | } from './departure' 14 | 15 | import { createGetNearestPlaces } from './nearest' 16 | 17 | import { 18 | createGetStopPlace, 19 | createGetStopPlaces, 20 | createGetParentStopPlace, 21 | createGetStopPlacesByPosition, 22 | createGetQuaysForStopPlace, 23 | } from './stopPlace' 24 | 25 | import { default as createGeocoderClient, GeocoderClient } from './geocoder' 26 | 27 | import { default as createMobilityClient, MobilityClient } from './mobility' 28 | 29 | import { default as createNsrClient, NsrClient } from './nsr' 30 | 31 | import { 32 | createGetBikeRentalStation, 33 | createGetBikeRentalStations, 34 | createGetBikeRentalStationsByPosition, 35 | } from './bikeRental' 36 | 37 | import { createGetFeatures, createGetFeaturesReverse } from './geocoderLegacy' 38 | 39 | import { ArgumentConfig, getServiceConfig } from './config' 40 | 41 | export interface EnturClient { 42 | queryJourneyPlanner: ( 43 | queryObj: string, 44 | variables: Record, 45 | options?: RequestOptions, 46 | ) => Promise 47 | queryNsr: ( 48 | queryObj: string, 49 | variables: Record, 50 | options?: RequestOptions, 51 | ) => Promise 52 | /** @deprecated Use geocoder.autocomplete instead. */ 53 | getFeatures: ReturnType 54 | /** @deprecated Use geocoder.reverse instead. */ 55 | getFeaturesReverse: ReturnType 56 | 57 | /** 58 | * @deprecated 59 | * The JourneyPlanner v2 queries and types are deprecated. 60 | * Write your own GraphQL queries for JourneyPlanner v3. 61 | * Write your own types or use those from JourneyPlannerTypes where applicable. 62 | */ 63 | getTripPatterns: ReturnType 64 | 65 | /** 66 | * @deprecated 67 | * The JourneyPlanner v2 queries and types are deprecated. 68 | * Write your own GraphQL queries for JourneyPlanner v3. 69 | * Write your own types or use those from JourneyPlannerTypes where applicable. 70 | */ 71 | findTrips: ReturnType 72 | 73 | /** 74 | * @deprecated 75 | * The JourneyPlanner v2 queries and types are deprecated. 76 | * Write your own GraphQL queries for JourneyPlanner v3. 77 | * Write your own types or use those from JourneyPlannerTypes where applicable. 78 | */ 79 | getStopPlaceDepartures: typeof getStopPlaceDeparturesDEPRECATED 80 | 81 | /** 82 | * @deprecated 83 | * The JourneyPlanner v2 queries and types are deprecated. 84 | * Write your own GraphQL queries for JourneyPlanner v3. 85 | * Write your own types or use those from JourneyPlannerTypes where applicable. 86 | */ 87 | getDeparturesFromStopPlace: ReturnType< 88 | typeof createGetDeparturesFromStopPlace 89 | > 90 | 91 | /** 92 | * @deprecated 93 | * The JourneyPlanner v2 queries and types are deprecated. 94 | * Write your own GraphQL queries for JourneyPlanner v3. 95 | * Write your own types or use those from JourneyPlannerTypes where applicable. 96 | */ 97 | getDeparturesFromStopPlaces: ReturnType< 98 | typeof createGetDeparturesFromStopPlaces 99 | > 100 | 101 | /** 102 | * @deprecated 103 | * The JourneyPlanner v2 queries and types are deprecated. 104 | * Write your own GraphQL queries for JourneyPlanner v3. 105 | * Write your own types or use those from JourneyPlannerTypes where applicable. 106 | */ 107 | getDeparturesFromQuays: ReturnType 108 | 109 | /** 110 | * @deprecated 111 | * The JourneyPlanner v2 queries and types are deprecated. 112 | * Write your own GraphQL queries for JourneyPlanner v3. 113 | * Write your own types or use those from JourneyPlannerTypes where applicable. 114 | */ 115 | getDeparturesBetweenStopPlaces: ReturnType< 116 | typeof createGetDeparturesBetweenStopPlaces 117 | > 118 | 119 | /** 120 | * @deprecated 121 | * The JourneyPlanner v2 queries and types are deprecated. 122 | * Write your own GraphQL queries for JourneyPlanner v3. 123 | * Write your own types or use those from JourneyPlannerTypes where applicable. 124 | */ 125 | getDeparturesForServiceJourney: ReturnType< 126 | typeof createGetDeparturesForServiceJourney 127 | > 128 | 129 | /** 130 | * @deprecated 131 | * The JourneyPlanner v2 queries and types are deprecated. 132 | * Write your own GraphQL queries for JourneyPlanner v3. 133 | * Write your own types or use those from JourneyPlannerTypes where applicable. 134 | */ 135 | getNearestPlaces: ReturnType 136 | 137 | /** 138 | * @deprecated 139 | * The JourneyPlanner v2 queries and types are deprecated. 140 | * Write your own GraphQL queries for JourneyPlanner v3. 141 | * Write your own types or use those from JourneyPlannerTypes where applicable. 142 | */ 143 | getStopPlace: ReturnType 144 | 145 | /** 146 | * @deprecated 147 | * The JourneyPlanner v2 queries and types are deprecated. 148 | * Write your own GraphQL queries for JourneyPlanner v3. 149 | * Write your own types or use those from JourneyPlannerTypes where applicable. 150 | */ 151 | getStopPlaces: ReturnType 152 | 153 | /** 154 | * @deprecated 155 | * The JourneyPlanner v2 queries and types are deprecated. 156 | * Write your own GraphQL queries for JourneyPlanner v3. 157 | * Write your own types or use those from JourneyPlannerTypes where applicable. 158 | */ 159 | getParentStopPlace: ReturnType 160 | 161 | /** 162 | * @deprecated 163 | * The JourneyPlanner v2 queries and types are deprecated. 164 | * Write your own GraphQL queries for JourneyPlanner v3. 165 | * Write your own types or use those from JourneyPlannerTypes where applicable. 166 | */ 167 | getStopPlacesByPosition: ReturnType 168 | 169 | /** 170 | * @deprecated 171 | * The JourneyPlanner v2 queries and types are deprecated. 172 | * Write your own GraphQL queries for JourneyPlanner v3. 173 | * Write your own types or use those from JourneyPlannerTypes where applicable. 174 | */ 175 | getQuaysForStopPlace: ReturnType 176 | 177 | /** 178 | * @deprecated 179 | * The JourneyPlanner v2 queries and types are deprecated. 180 | * Write your own GraphQL queries for JourneyPlanner v3. 181 | * Write your own types or use those from JourneyPlannerTypes where applicable. 182 | */ 183 | getBikeRentalStation: ReturnType 184 | 185 | /** 186 | * @deprecated 187 | * The JourneyPlanner v2 queries and types are deprecated. 188 | * Write your own GraphQL queries for JourneyPlanner v3. 189 | * Write your own types or use those from JourneyPlannerTypes where applicable. 190 | */ 191 | getBikeRentalStations: ReturnType 192 | 193 | /** 194 | * @deprecated 195 | * The JourneyPlanner v2 queries and types are deprecated. 196 | * Write your own GraphQL queries for JourneyPlanner v3. 197 | * Write your own types or use those from JourneyPlannerTypes where applicable. 198 | */ 199 | getBikeRentalStationsByPosition: ReturnType< 200 | typeof createGetBikeRentalStationsByPosition 201 | > 202 | geocoder: GeocoderClient 203 | mobility: MobilityClient 204 | nsr: NsrClient 205 | } 206 | 207 | /** 208 | * @deprecated Use `EnturClient` instead. 209 | */ 210 | export type EnturService = EnturClient 211 | 212 | function createEnturClient(config: ArgumentConfig): EnturClient { 213 | return { 214 | queryJourneyPlanner: ( 215 | queryObj: string, 216 | variables: Record, 217 | options?: RequestOptions, 218 | ): Promise => 219 | journeyPlannerQuery( 220 | queryObj, 221 | variables, 222 | getServiceConfig(config), 223 | options, 224 | ), 225 | queryNsr: ( 226 | queryObj: string, 227 | variables: Record, 228 | options?: RequestOptions, 229 | ): Promise => 230 | nsrQuery(queryObj, variables, getServiceConfig(config), options), 231 | getFeatures: createGetFeatures(config), 232 | getFeaturesReverse: createGetFeaturesReverse(config), 233 | getTripPatterns: createGetTripPatterns(config), 234 | findTrips: createFindTrips(config), 235 | getStopPlaceDepartures: getStopPlaceDeparturesDEPRECATED, 236 | getDeparturesFromStopPlace: createGetDeparturesFromStopPlace(config), 237 | getDeparturesFromStopPlaces: createGetDeparturesFromStopPlaces(config), 238 | getDeparturesFromQuays: createGetDeparturesFromQuays(config), 239 | getDeparturesBetweenStopPlaces: 240 | createGetDeparturesBetweenStopPlaces(config), 241 | getDeparturesForServiceJourney: 242 | createGetDeparturesForServiceJourney(config), 243 | getNearestPlaces: createGetNearestPlaces(config), 244 | getStopPlace: createGetStopPlace(config), 245 | getStopPlaces: createGetStopPlaces(config), 246 | getParentStopPlace: createGetParentStopPlace(config), 247 | getStopPlacesByPosition: createGetStopPlacesByPosition(config), 248 | getQuaysForStopPlace: createGetQuaysForStopPlace(config), 249 | getBikeRentalStation: createGetBikeRentalStation(config), 250 | getBikeRentalStations: createGetBikeRentalStations(config), 251 | getBikeRentalStationsByPosition: 252 | createGetBikeRentalStationsByPosition(config), 253 | geocoder: createGeocoderClient(config), 254 | mobility: createMobilityClient(config), 255 | nsr: createNsrClient(config), 256 | } 257 | } 258 | 259 | export default createEnturClient 260 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import { Response, RequestInfo, RequestInit } from 'node-fetch' 2 | 3 | export interface HostConfig { 4 | host: string 5 | headers?: { [key: string]: string } 6 | } 7 | 8 | export interface ServiceConfig { 9 | clientName: string 10 | hosts: { 11 | journeyPlanner: string 12 | geocoder: string 13 | nsr: string 14 | scooters: string 15 | mobility: string 16 | } 17 | headers: { [key: string]: string } 18 | fetch?: ( 19 | url: RequestInfo, 20 | init?: RequestInit | undefined, 21 | ) => Promise 22 | } 23 | 24 | export interface ArgumentConfig { 25 | clientName: string 26 | hosts?: { 27 | journeyPlanner?: string 28 | geocoder?: string 29 | nsr?: string 30 | scooters?: string 31 | mobility?: string 32 | } 33 | headers?: { [key: string]: string } 34 | fetch?: ( 35 | url: RequestInfo, 36 | init?: RequestInit | undefined, 37 | ) => Promise 38 | } 39 | 40 | export interface OverrideConfig { 41 | clientName?: string 42 | hosts?: { 43 | journeyPlanner?: string 44 | geocoder?: string 45 | nsr?: string 46 | scooters?: string 47 | mobility?: string 48 | } 49 | headers?: { [key: string]: string } 50 | fetch?: ( 51 | url: RequestInfo, 52 | init?: RequestInit | undefined, 53 | ) => Promise 54 | } 55 | 56 | const HOST_CONFIG = { 57 | journeyPlanner: 'https://api.entur.io/journey-planner/v2', 58 | geocoder: 'https://api.entur.io/geocoder/v1', 59 | nsr: 'https://api.entur.io/stop-places/v1', 60 | scooters: 'https://api.entur.io/mobility/v1/scooters', 61 | mobility: 'https://api.entur.io/mobility/v2', 62 | } 63 | 64 | export function getServiceConfig(config: ArgumentConfig): ServiceConfig { 65 | if (!config || !config.clientName) { 66 | throw new Error( 67 | 'ERROR: You must pass a "clientName" to EnturClient through the config argument. ' + 68 | 'See https://www.entur.org/dev/api/header/ for information.\n', 69 | ) 70 | } 71 | 72 | const { clientName, hosts = {}, headers = {}, fetch } = config 73 | 74 | return { 75 | clientName, 76 | headers, 77 | hosts: { 78 | ...HOST_CONFIG, 79 | ...hosts, 80 | }, 81 | fetch, 82 | } 83 | } 84 | 85 | export function getJourneyPlannerHost({ 86 | hosts, 87 | clientName, 88 | headers, 89 | }: ServiceConfig): HostConfig { 90 | return { 91 | host: hosts.journeyPlanner, 92 | headers: { 93 | 'ET-Client-Name': clientName, 94 | ...headers, 95 | }, 96 | } 97 | } 98 | 99 | export function getGeocoderHost({ 100 | hosts, 101 | clientName, 102 | headers, 103 | }: ServiceConfig): HostConfig { 104 | return { 105 | host: hosts.geocoder, 106 | headers: { 107 | 'ET-Client-Name': clientName, 108 | ...headers, 109 | }, 110 | } 111 | } 112 | 113 | export function getNSRHost({ 114 | hosts, 115 | clientName, 116 | headers, 117 | }: ServiceConfig): HostConfig { 118 | return { 119 | host: hosts.nsr, 120 | headers: { 121 | 'ET-Client-Name': clientName, 122 | ...headers, 123 | }, 124 | } 125 | } 126 | 127 | export function getScootersHost({ 128 | hosts, 129 | clientName, 130 | headers, 131 | }: ServiceConfig): HostConfig { 132 | return { 133 | host: hosts.scooters, 134 | headers: { 135 | 'ET-Client-Name': clientName, 136 | ...headers, 137 | }, 138 | } 139 | } 140 | 141 | export function getMobilityHost({ 142 | hosts, 143 | clientName, 144 | headers, 145 | }: ServiceConfig): HostConfig { 146 | return { 147 | host: hosts.mobility, 148 | headers: { 149 | 'ET-Client-Name': clientName, 150 | ...headers, 151 | }, 152 | } 153 | } 154 | 155 | export function mergeConfig( 156 | config: ServiceConfig, 157 | override?: OverrideConfig, 158 | ): ServiceConfig { 159 | if (!override) { 160 | return config 161 | } 162 | 163 | return { 164 | ...config, 165 | clientName: override.clientName || config.clientName, 166 | fetch: override.fetch || config.fetch, 167 | hosts: { 168 | ...config.hosts, 169 | ...override.hosts, 170 | }, 171 | headers: { 172 | ...config.headers, 173 | ...override.headers, 174 | }, 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/constants/featureCategory.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @deprecated The Feature type is deprecated, use the geocoder.autocomplete and geocoder.reverse methods instead and related types. 3 | */ 4 | export enum FeatureCategory { 5 | ONSTREET_BUS = 'onstreetBus', 6 | ONSTREET_TRAM = 'onstreetTram', 7 | AIRPORT = 'airport', 8 | RAIL_STATION = 'railStation', 9 | METRO_STATION = 'metroStation', 10 | BUS_STATION = 'busStation', 11 | COACH_STATION = 'coachStation', 12 | TRAM_STATION = 'tramStation', 13 | HARBOUR_PORT = 'harbourPort', 14 | FERRY_PORT = 'ferryPort', 15 | FERRY_STOP = 'ferryStop', 16 | LIFT_STATION = 'liftStation', 17 | VEHICLE_RAIL_INTERCHANGE = 'vehicleRailInterchange', 18 | GROUP_OF_STOP_PLACES = 'GroupOfStopPlaces', 19 | POI = 'poi', 20 | VEGADRESSE = 'Vegadresse', 21 | STREET = 'street', 22 | TETTSTEDDEL = 'tettsteddel', 23 | BYDEL = 'bydel', 24 | OTHER = 'other', 25 | } 26 | -------------------------------------------------------------------------------- /src/constants/rateLimits.ts: -------------------------------------------------------------------------------- 1 | export const MAX_CALLS_PER_SECOND = 10 2 | export const MAX_CALLS_PER_MINUTE = 200 3 | -------------------------------------------------------------------------------- /src/departure/index.ts: -------------------------------------------------------------------------------- 1 | import { RequestOptions } from '../http' 2 | import { journeyPlannerQuery } from '../api' 3 | 4 | import { forceOrder } from '../utils' 5 | 6 | import { Departure } from '../fields/Departure' 7 | 8 | import { 9 | getDeparturesFromStopPlacesQuery, 10 | getDeparturesFromQuayQuery, 11 | getDeparturesBetweenStopPlacesQuery, 12 | getDeparturesForServiceJourneyQuery, 13 | } from './query' 14 | 15 | import { 16 | destinationMapper, 17 | legToDepartureMapper, 18 | LegWithDepartures, 19 | } from './mapper' 20 | 21 | import { getServiceConfig, ArgumentConfig } from '../config' 22 | import { isTruthy } from '../utils' 23 | import { QueryMode } from '../types/Mode' 24 | 25 | export type DeparturesById = { 26 | id: string 27 | departures: Departure[] 28 | } 29 | 30 | type GetDeparturesParams = { 31 | includeCancelledTrips?: boolean 32 | includeNonBoarding?: boolean 33 | limit?: number 34 | limitPerLine?: number 35 | start?: Date 36 | timeRange?: number 37 | whiteListedLines?: string[] 38 | whiteListedAuthorities?: string[] 39 | whiteListedModes?: string[] 40 | } 41 | 42 | export function createGetDeparturesFromStopPlaces(argConfig: ArgumentConfig) { 43 | const config = getServiceConfig(argConfig) 44 | 45 | return function getDeparturesFromStopPlaces( 46 | stopPlaceIds: string[], 47 | params: GetDeparturesParams = {}, 48 | options?: RequestOptions, 49 | ): Promise> { 50 | if (!Array.isArray(stopPlaceIds)) { 51 | throw new Error( 52 | `getDeparturesFromStopPlaces takes an array of strings, but got ${typeof stopPlaceIds}`, 53 | ) 54 | } 55 | 56 | if (stopPlaceIds.length === 0) { 57 | return Promise.resolve([]) 58 | } 59 | 60 | const { 61 | limit = 50, 62 | timeRange = 72000, 63 | start = new Date(), 64 | limitPerLine, 65 | includeCancelledTrips = false, 66 | includeNonBoarding = false, 67 | whiteListedLines, 68 | whiteListedAuthorities, 69 | whiteListedModes, 70 | ...rest 71 | } = params 72 | 73 | const variables = { 74 | ids: stopPlaceIds, 75 | includeCancelledTrips, 76 | start: start.toISOString(), 77 | omitNonBoarding: !includeNonBoarding, 78 | timeRange, 79 | limit, 80 | limitPerLine, 81 | whiteListedLines, 82 | whiteListedAuthorities, 83 | whiteListedModes, 84 | ...rest, 85 | } 86 | 87 | return journeyPlannerQuery<{ 88 | stopPlaces?: Array<{ id: string; estimatedCalls: Departure[] }> 89 | }>(getDeparturesFromStopPlacesQuery, variables, config, options) 90 | .then((data) => { 91 | if (!data?.stopPlaces) { 92 | throw new Error( 93 | `Missing data: getDeparturesFromStopPlaces received no data from the API.`, 94 | ) 95 | } 96 | 97 | return data.stopPlaces.map( 98 | ({ estimatedCalls, ...stopPlace }) => ({ 99 | ...stopPlace, 100 | departures: estimatedCalls.map(destinationMapper), 101 | }), 102 | ) 103 | }) 104 | .then((stopPlaces: DeparturesById[]) => { 105 | return forceOrder(stopPlaces, stopPlaceIds, ({ id }) => id) 106 | }) 107 | } 108 | } 109 | 110 | export function createGetDeparturesFromStopPlace(argConfig: ArgumentConfig) { 111 | const getDeparturesFromStopPlaces = 112 | createGetDeparturesFromStopPlaces(argConfig) 113 | 114 | /** 115 | * Finds departures that leaves from a certain StopPlace. 116 | */ 117 | return function getDeparturesFromStopPlace( 118 | stopPlaceId: string, 119 | params?: GetDeparturesParams, 120 | ): Promise { 121 | return getDeparturesFromStopPlaces([stopPlaceId], params).then( 122 | (stopPlaces: Array) => { 123 | if (!stopPlaces?.length || !stopPlaces[0]) return [] 124 | return stopPlaces[0].departures || [] 125 | }, 126 | ) 127 | } 128 | } 129 | 130 | export function createGetDeparturesFromQuays(argConfig: ArgumentConfig) { 131 | const config = getServiceConfig(argConfig) 132 | 133 | return function getDeparturesFromQuays( 134 | quayIds: string[], 135 | params: GetDeparturesParams = {}, 136 | options?: RequestOptions, 137 | ): Promise> { 138 | if (!Array.isArray(quayIds)) { 139 | throw new Error( 140 | `getDeparturesFromQuays takes an array of strings, but got ${typeof quayIds}`, 141 | ) 142 | } 143 | 144 | if (quayIds.length === 0) { 145 | return Promise.resolve([]) 146 | } 147 | 148 | const { 149 | limit = 30, 150 | limitPerLine, 151 | timeRange = 72000, 152 | includeCancelledTrips = false, 153 | includeNonBoarding = false, 154 | start = new Date(), 155 | ...rest 156 | } = params 157 | 158 | const variables = { 159 | ids: quayIds, 160 | start: start.toISOString(), 161 | includeCancelledTrips, 162 | omitNonBoarding: !includeNonBoarding, 163 | timeRange, 164 | limit, 165 | limitPerLine, 166 | ...rest, 167 | } 168 | return journeyPlannerQuery<{ 169 | quays?: Array<{ estimatedCalls: Departure[]; id: string }> 170 | }>(getDeparturesFromQuayQuery, variables, config, options) 171 | .then((data) => { 172 | if (!data || !data?.quays) { 173 | throw new Error( 174 | `Missing data: getDeparturesFromQuays received no data from the API.`, 175 | ) 176 | } 177 | 178 | return data.quays.map(({ estimatedCalls, ...stopPlace }) => ({ 179 | ...stopPlace, 180 | departures: estimatedCalls.map(destinationMapper), 181 | })) 182 | }) 183 | .then((quayDepartures: DeparturesById[]) => { 184 | return forceOrder(quayDepartures, quayIds, ({ id }) => id) 185 | }) 186 | } 187 | } 188 | 189 | export type GetDeparturesBetweenStopPlacesParams = { 190 | limit?: number 191 | start?: Date 192 | } 193 | export function createGetDeparturesBetweenStopPlaces( 194 | argConfig: ArgumentConfig, 195 | ) { 196 | const config = getServiceConfig(argConfig) 197 | 198 | return function getDeparturesBetweenStopPlaces( 199 | fromStopPlaceId: string, 200 | toStopPlaceId: string, 201 | params: GetDeparturesBetweenStopPlacesParams = {}, 202 | options?: RequestOptions, 203 | ): Promise { 204 | const { limit = 20, start = new Date(), ...rest } = params 205 | const variables = { 206 | from: { place: fromStopPlaceId }, 207 | to: { place: toStopPlaceId }, 208 | limit, 209 | dateTime: start.toISOString(), 210 | arriveBy: false, 211 | modes: [ 212 | QueryMode.BUS, 213 | QueryMode.TRAM, 214 | QueryMode.RAIL, 215 | QueryMode.METRO, 216 | QueryMode.WATER, 217 | QueryMode.AIR, 218 | QueryMode.COACH, 219 | QueryMode.CAR, 220 | ], 221 | ...rest, 222 | } 223 | 224 | return journeyPlannerQuery<{ 225 | trip: { tripPatterns: Array<{ legs: LegWithDepartures[] }> } 226 | }>( 227 | getDeparturesBetweenStopPlacesQuery, 228 | variables, 229 | config, 230 | options, 231 | ).then((data) => { 232 | if (!data || !data?.trip?.tripPatterns) return [] 233 | 234 | return data.trip.tripPatterns 235 | .map((trip) => { 236 | const [leg] = trip.legs 237 | if (!leg) return undefined 238 | 239 | return legToDepartureMapper(leg) 240 | }) 241 | .filter(isTruthy) 242 | }) 243 | } 244 | } 245 | 246 | export function createGetDeparturesForServiceJourney( 247 | argConfig: ArgumentConfig, 248 | ) { 249 | const config = getServiceConfig(argConfig) 250 | 251 | return function getDeparturesForServiceJourney( 252 | id: string, 253 | date?: string, 254 | options?: RequestOptions, 255 | ): Promise { 256 | const variables = { 257 | id, 258 | date, 259 | } 260 | 261 | return journeyPlannerQuery<{ 262 | serviceJourney?: { estimatedCalls: Departure[] } 263 | }>( 264 | getDeparturesForServiceJourneyQuery, 265 | variables, 266 | config, 267 | options, 268 | ).then((data) => { 269 | return (data?.serviceJourney?.estimatedCalls || []).map( 270 | destinationMapper, 271 | ) 272 | }) 273 | } 274 | } 275 | 276 | export function getStopPlaceDeparturesDEPRECATED() { 277 | throw new Error( 278 | 'Entur SDK: "getStopPlaceDepartures" is deprecated, use "getDeparturesForStopPlace" or getDeparturesForStopPlaces instead.', 279 | ) 280 | } 281 | -------------------------------------------------------------------------------- /src/departure/mapper.ts: -------------------------------------------------------------------------------- 1 | import { Departure } from '../fields/Departure' 2 | import { Notice } from '../fields/Notice' 3 | import { Place } from '../fields/Place' 4 | import { ServiceJourney } from '../fields/ServiceJourney' 5 | import { Situation } from '../fields/Situation' 6 | 7 | import { uniqBy } from '../utils' 8 | 9 | export interface LegWithDepartures { 10 | aimedStartTime: string 11 | expectedStartTime: string 12 | fromEstimatedCall?: Departure 13 | fromPlace: Place 14 | realtime: boolean 15 | serviceJourney: ServiceJourney 16 | situations: Situation[] 17 | } 18 | 19 | function getNoticesFromLeg(leg: LegWithDepartures): Notice[] { 20 | const notices = [ 21 | ...(leg.serviceJourney?.notices || []), 22 | ...(leg.serviceJourney?.journeyPattern?.notices || []), 23 | ...(leg.serviceJourney?.journeyPattern?.line?.notices || []), 24 | ...(leg.fromEstimatedCall?.notices || []), 25 | ] 26 | return uniqBy(notices, (notice) => notice.text) 27 | } 28 | 29 | function getNotices(departure: Departure): Notice[] { 30 | const notices = [ 31 | ...(departure.notices || []), 32 | ...(departure.serviceJourney?.notices || []), 33 | ...(departure.serviceJourney?.journeyPattern?.notices || []), 34 | ...(departure.serviceJourney?.journeyPattern?.line?.notices || []), 35 | ] 36 | return uniqBy(notices, (notice) => notice.text) 37 | } 38 | 39 | export function destinationMapper(departure: Departure): Departure { 40 | return { 41 | ...departure, 42 | notices: getNotices(departure), 43 | } 44 | } 45 | 46 | export function legToDepartureMapper( 47 | leg: LegWithDepartures, 48 | ): Departure | undefined { 49 | const { fromEstimatedCall } = leg 50 | 51 | if (!fromEstimatedCall) return undefined 52 | 53 | return { 54 | actualArrivalTime: fromEstimatedCall.actualArrivalTime, 55 | actualDepartureTime: fromEstimatedCall.actualDepartureTime, 56 | aimedArrivalTime: fromEstimatedCall.aimedArrivalTime, 57 | aimedDepartureTime: leg.aimedStartTime, 58 | cancellation: fromEstimatedCall.cancellation, 59 | date: fromEstimatedCall.date, 60 | destinationDisplay: fromEstimatedCall.destinationDisplay, 61 | expectedArrivalTime: fromEstimatedCall.expectedArrivalTime, 62 | expectedDepartureTime: leg.expectedStartTime, 63 | forAlighting: fromEstimatedCall.forAlighting, 64 | forBoarding: fromEstimatedCall.forBoarding, 65 | notices: getNoticesFromLeg(leg), 66 | predictionInaccurate: fromEstimatedCall.predictionInaccurate, 67 | quay: leg.fromPlace.quay, 68 | realtime: leg.realtime, 69 | requestStop: fromEstimatedCall.requestStop, 70 | serviceJourney: leg.serviceJourney, 71 | situations: leg.situations || [], 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/departure/query.ts: -------------------------------------------------------------------------------- 1 | import { 2 | fragmentName as quayFields, 3 | fragments as quayFragments, 4 | } from '../fields/Quay' 5 | 6 | import { 7 | fragmentName as serviceJourneyFields, 8 | fragments as serviceJourneyFragments, 9 | } from '../fields/ServiceJourney' 10 | 11 | import { 12 | fragmentName as departureFields, 13 | fragments as departureFragments, 14 | } from '../fields/Departure' 15 | 16 | import { 17 | fragmentName as situationFields, 18 | fragments as situationFragments, 19 | } from '../fields/Situation' 20 | 21 | import { uniq } from '../utils' 22 | 23 | export const getDeparturesFromStopPlacesQuery = ` 24 | query( 25 | $ids: [String]!, 26 | $start: DateTime!, 27 | $timeRange: Int!, 28 | $limit: Int!, 29 | $limitPerLine: Int, 30 | $omitNonBoarding: Boolean!, 31 | $whiteListedLines: [String!], 32 | $whiteListedAuthorities: [String!], 33 | $whiteListedModes: [Mode], 34 | $includeCancelledTrips: Boolean! 35 | ) { 36 | stopPlaces(ids: $ids) { 37 | id 38 | estimatedCalls( 39 | startTime: $start, 40 | timeRange: $timeRange, 41 | numberOfDepartures: $limit, 42 | numberOfDeparturesPerLineAndDestinationDisplay: $limitPerLine, 43 | omitNonBoarding: $omitNonBoarding, 44 | whiteListed: { 45 | lines: $whiteListedLines, 46 | authorities: $whiteListedAuthorities, 47 | }, 48 | whiteListedModes: $whiteListedModes, 49 | includeCancelledTrips: $includeCancelledTrips 50 | ) { 51 | ...${departureFields} 52 | } 53 | } 54 | } 55 | 56 | ${departureFragments.join('')} 57 | ` 58 | 59 | export const getDeparturesFromQuayQuery = ` 60 | query( 61 | $ids: [String]!, 62 | $start: DateTime!, 63 | $timeRange: Int!, 64 | $limit: Int!, 65 | $limitPerLine: Int, 66 | $omitNonBoarding: Boolean!, 67 | $includeCancelledTrips: Boolean! 68 | ) { 69 | quays(ids: $ids) { 70 | id 71 | estimatedCalls( 72 | startTime: $start, 73 | timeRange: $timeRange, 74 | numberOfDepartures: $limit, 75 | omitNonBoarding: $omitNonBoarding, 76 | includeCancelledTrips: $includeCancelledTrips, 77 | numberOfDeparturesPerLineAndDestinationDisplay: $limitPerLine 78 | ) { 79 | ...${departureFields} 80 | } 81 | } 82 | } 83 | 84 | ${departureFragments.join('')} 85 | ` 86 | 87 | export const getDeparturesBetweenStopPlacesQuery = ` 88 | query( 89 | $from: Location!, 90 | $to: Location!, 91 | $limit: Int!, 92 | $dateTime: DateTime!, 93 | $arriveBy: Boolean!, 94 | $modes: [Mode]!, 95 | $transportSubmodes: [TransportSubmodeFilter] 96 | ) { 97 | trip( 98 | from: $from, 99 | to: $to, 100 | numTripPatterns: $limit, 101 | dateTime: $dateTime, 102 | arriveBy: $arriveBy, 103 | modes: $modes, 104 | transportSubmodes: $transportSubmodes, 105 | wheelchair: false, 106 | maximumTransfers: 0 107 | ) { 108 | tripPatterns { 109 | legs { 110 | aimedStartTime 111 | expectedStartTime 112 | fromPlace { 113 | quay { 114 | ...${quayFields} 115 | } 116 | } 117 | realtime 118 | serviceJourney { 119 | ...${serviceJourneyFields} 120 | } 121 | situations { 122 | ...${situationFields} 123 | } 124 | fromEstimatedCall { 125 | ...${departureFields} 126 | } 127 | } 128 | } 129 | } 130 | } 131 | 132 | ${uniq([ 133 | ...departureFragments, 134 | ...quayFragments, 135 | ...serviceJourneyFragments, 136 | ...situationFragments, 137 | ]).join('')} 138 | ` 139 | 140 | export const getDeparturesForServiceJourneyQuery = ` 141 | query( 142 | $id: String!, 143 | $date: Date, 144 | ) { 145 | serviceJourney(id: $id) { 146 | estimatedCalls(date: $date) { 147 | ...${departureFields} 148 | } 149 | } 150 | } 151 | 152 | ${departureFragments.join('')} 153 | ` 154 | -------------------------------------------------------------------------------- /src/fetch.browser.ts: -------------------------------------------------------------------------------- 1 | export default fetch 2 | -------------------------------------------------------------------------------- /src/fetch.ts: -------------------------------------------------------------------------------- 1 | import nodeFetch from 'node-fetch' 2 | 3 | export default nodeFetch 4 | -------------------------------------------------------------------------------- /src/fields/Authority.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @deprecated 3 | * The JourneyPlanner v2 queries and types are deprecated. 4 | * Write your own GraphQL queries for JourneyPlanner v3. 5 | * Write your own types or use those from JourneyPlannerTypes where applicable. 6 | */ 7 | export interface Authority { 8 | codeSpace: string // Added by mapper 9 | id: string 10 | name: string 11 | url?: string 12 | } 13 | 14 | export const fragmentName = 'authorityFields' 15 | 16 | const fragment = ` 17 | fragment ${fragmentName} on Authority { 18 | id 19 | name 20 | url 21 | } 22 | ` 23 | 24 | export const fragments = [fragment] 25 | -------------------------------------------------------------------------------- /src/fields/BikeRentalStation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @deprecated 3 | * The JourneyPlanner v2 queries and types are deprecated. 4 | * Write your own GraphQL queries for JourneyPlanner v3. 5 | * Write your own types or use those from JourneyPlannerTypes where applicable. 6 | */ 7 | export interface BikeRentalStation { 8 | id: string 9 | name: string 10 | networks: string[] 11 | spacesAvailable?: number 12 | bikesAvailable?: number 13 | longitude: number 14 | latitude: number 15 | } 16 | 17 | export const fragmentName = 'bikeRentalStationFields' 18 | 19 | const fragment = ` 20 | fragment ${fragmentName} on BikeRentalStation { 21 | id 22 | name 23 | networks 24 | bikesAvailable 25 | spacesAvailable 26 | longitude 27 | latitude 28 | }` 29 | 30 | export const fragments = [fragment] 31 | -------------------------------------------------------------------------------- /src/fields/BookingArrangement.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @deprecated 3 | * The JourneyPlanner v2 queries and types are deprecated. 4 | * Write your own GraphQL queries for JourneyPlanner v3. 5 | * Write your own types or use those from JourneyPlannerTypes where applicable. 6 | */ 7 | export enum BookingMethod { 8 | CALL_DRIVER = 'callDriver', 9 | CALL_OFFICE = 'callOffice', 10 | ONLINE = 'online', 11 | OTHER = 'other', 12 | PHONE_AT_STOP = 'phoneAtStop', 13 | TEXT = 'text', 14 | NONE = 'none', 15 | } 16 | 17 | /** 18 | * @deprecated 19 | * The JourneyPlanner v2 queries and types are deprecated. 20 | * Write your own GraphQL queries for JourneyPlanner v3. 21 | * Write your own types or use those from JourneyPlannerTypes where applicable. 22 | */ 23 | export enum BookWhen { 24 | ADVANCE_ONLY = 'advanceOnly', 25 | UNTIL_PREVIOUS_DAY = 'untilPreviousDay', 26 | DAY_OF_TRAVEL_ONLY = 'dayOfTravelOnly', 27 | ADVANCE_AND_DAY_OF_TRAVEL = 'advanceAndDayOfTravel', 28 | TIME_OF_TRAVEL_ONLY = 'timeOfTravelOnly', 29 | SUBSCRIPTION_CHARGE_MOMENT = 'subscriptionChargeMoment', 30 | OTHER = 'other', 31 | } 32 | 33 | /** 34 | * @deprecated 35 | * The JourneyPlanner v2 queries and types are deprecated. 36 | * Write your own GraphQL queries for JourneyPlanner v3. 37 | * Write your own types or use those from JourneyPlannerTypes where applicable. 38 | */ 39 | export enum BuyWhen { 40 | ON_RESERVATION = 'onReservation', 41 | IN_ADVANCE = 'inAdvance', 42 | IN_ADVANCE_ONLY = 'inAdvanceOnly', 43 | BEFORE_BOARDING = 'beforeBoarding', 44 | BEFORE_BOARDING_ONLY = 'beforeBoardingOnly', 45 | ON_BOARDING = 'onBoarding', 46 | ON_BOARDING_ONLY = 'onBoardingOnly', 47 | AFTER_BOARDING = 'afterBoarding', 48 | ON_CHECK_IN = 'onCheckIn', 49 | ON_CHECK_OUT = 'onCheckOut', 50 | SUBSCRIPTION_ONLY = 'subscriptionOnly', 51 | OTHER = 'other', 52 | } 53 | 54 | /** 55 | * @deprecated 56 | * The JourneyPlanner v2 queries and types are deprecated. 57 | * Write your own GraphQL queries for JourneyPlanner v3. 58 | * Write your own types or use those from JourneyPlannerTypes where applicable. 59 | */ 60 | export enum BookingAccess { 61 | PUBLIC_ACCESS = 'publicAccess', 62 | AUTHORISED_PUBLIC = 'authorisedPublic', 63 | STAFF = 'staff', 64 | OTHER = 'other', 65 | } 66 | 67 | /** 68 | * @deprecated 69 | * The JourneyPlanner v2 queries and types are deprecated. 70 | * Write your own GraphQL queries for JourneyPlanner v3. 71 | * Write your own types or use those from JourneyPlannerTypes where applicable. 72 | */ 73 | export interface BookingContact { 74 | contactPerson?: string 75 | email?: string 76 | furtherDetails?: string 77 | phone?: string 78 | url?: string 79 | } 80 | 81 | /** 82 | * @deprecated 83 | * The JourneyPlanner v2 queries and types are deprecated. 84 | * Write your own GraphQL queries for JourneyPlanner v3. 85 | * Write your own types or use those from JourneyPlannerTypes where applicable. 86 | */ 87 | export interface BookingArrangement { 88 | bookingAccess?: BookingAccess 89 | bookingContact?: BookingContact 90 | bookingMethods?: BookingMethod[] 91 | bookingNote?: string 92 | bookWhen?: BookWhen 93 | buyWhen?: BuyWhen[] 94 | latestBookingTime?: string 95 | minimumBookingPeriod?: string 96 | } 97 | 98 | export const fragmentName = 'bookingArrangementFields' 99 | 100 | const fragment = ` 101 | fragment ${fragmentName} on BookingArrangement { 102 | bookingAccess 103 | bookingContact { 104 | contactPerson 105 | email 106 | url 107 | phone 108 | furtherDetails 109 | } 110 | bookingMethods 111 | bookingNote 112 | bookWhen 113 | buyWhen 114 | latestBookingTime 115 | minimumBookingPeriod 116 | } 117 | ` 118 | 119 | export const fragments = [fragment] 120 | -------------------------------------------------------------------------------- /src/fields/Departure.ts: -------------------------------------------------------------------------------- 1 | import { uniq } from '../utils' 2 | 3 | import { 4 | fragmentName as noticeFields, 5 | fragments as noticeFragments, 6 | Notice, 7 | } from './Notice' 8 | 9 | import { 10 | fragmentName as quayFields, 11 | fragments as quayFragments, 12 | Quay, 13 | } from './Quay' 14 | 15 | import { 16 | fragmentName as serviceJourneyFields, 17 | fragments as serviceJourneyFragments, 18 | ServiceJourney, 19 | } from './ServiceJourney' 20 | 21 | import { 22 | fragmentName as situationFields, 23 | fragments as situationFragments, 24 | Situation, 25 | } from './Situation' 26 | 27 | /** 28 | * @deprecated 29 | * The JourneyPlanner v2 queries and types are deprecated. 30 | * Write your own GraphQL queries for JourneyPlanner v3. 31 | * Write your own types or use those from JourneyPlannerTypes where applicable. 32 | */ 33 | export interface Departure { 34 | actualArrivalTime?: string // Only available AFTER arrival has taken place 35 | actualDepartureTime?: string // Only available AFTER departure has taken place 36 | aimedArrivalTime: string 37 | aimedDepartureTime: string 38 | cancellation: boolean 39 | date: string 40 | destinationDisplay: { 41 | frontText: string 42 | } 43 | expectedArrivalTime: string 44 | expectedDepartureTime: string 45 | forAlighting: boolean 46 | forBoarding: boolean 47 | notices?: Notice[] 48 | predictionInaccurate: boolean 49 | quay?: Quay 50 | realtime: boolean 51 | requestStop: boolean 52 | serviceJourney: ServiceJourney 53 | situations: Situation[] 54 | } 55 | 56 | export const fragmentName = 'estimatedCallFields' 57 | 58 | const fragment = ` 59 | fragment ${fragmentName} on EstimatedCall { 60 | actualArrivalTime 61 | actualDepartureTime 62 | aimedArrivalTime 63 | aimedDepartureTime 64 | cancellation 65 | date 66 | destinationDisplay { 67 | frontText 68 | } 69 | expectedDepartureTime 70 | expectedArrivalTime 71 | forAlighting 72 | forBoarding 73 | notices { 74 | ...${noticeFields} 75 | } 76 | predictionInaccurate 77 | quay { 78 | ...${quayFields} 79 | } 80 | realtime 81 | requestStop 82 | serviceJourney { 83 | ...${serviceJourneyFields} 84 | } 85 | situations { 86 | ...${situationFields} 87 | } 88 | }` 89 | 90 | export const fragments = uniq([ 91 | fragment, 92 | ...noticeFragments, 93 | ...quayFragments, 94 | ...serviceJourneyFragments, 95 | ...situationFragments, 96 | ]) 97 | -------------------------------------------------------------------------------- /src/fields/EstimatedCall.ts: -------------------------------------------------------------------------------- 1 | import { uniq } from '../utils' 2 | 3 | import { 4 | fragmentName as noticeFields, 5 | fragments as noticeFragments, 6 | Notice, 7 | } from './Notice' 8 | 9 | import { 10 | fragmentName as quayFields, 11 | fragments as quayFragments, 12 | Quay, 13 | } from './Quay' 14 | 15 | import { 16 | fragmentName as serviceJourneyFields, 17 | fragments as serviceJourneyFragments, 18 | ServiceJourney, 19 | } from './ServiceJourney' 20 | 21 | import { 22 | fragmentName as bookingArrangementFields, 23 | fragments as bookingArrangementFragments, 24 | BookingArrangement, 25 | } from './BookingArrangement' 26 | 27 | /** 28 | * @deprecated 29 | * The JourneyPlanner v2 queries and types are deprecated. 30 | * Write your own GraphQL queries for JourneyPlanner v3. 31 | * Write your own types or use those from JourneyPlannerTypes where applicable. 32 | */ 33 | export interface EstimatedCall { 34 | actualArrivalTime?: string // Only available AFTER arrival has taken place 35 | actualDepartureTime?: string // Only available AFTER departure has taken place 36 | aimedArrivalTime: string 37 | aimedDepartureTime: string 38 | /** @deprecated Use bookingArrangements on Leg or ServiceJourney instead. */ 39 | bookingArrangements?: BookingArrangement 40 | cancellation: boolean 41 | date: string 42 | destinationDisplay: { 43 | frontText: string 44 | } 45 | expectedArrivalTime: string 46 | expectedDepartureTime: string 47 | forAlighting: boolean 48 | forBoarding: boolean 49 | notices?: Notice[] 50 | predictionInaccurate: boolean 51 | quay?: Quay 52 | realtime: boolean 53 | requestStop: boolean 54 | serviceJourney: ServiceJourney 55 | } 56 | 57 | export type IntermediateEstimatedCall = EstimatedCall 58 | 59 | export const fragmentName = 'estimatedCallFields' 60 | 61 | const fragment = ` 62 | fragment ${fragmentName} on EstimatedCall { 63 | actualArrivalTime 64 | actualDepartureTime 65 | aimedArrivalTime 66 | aimedDepartureTime 67 | bookingArrangements { 68 | ...${bookingArrangementFields} 69 | } 70 | cancellation 71 | date 72 | destinationDisplay { 73 | frontText 74 | } 75 | expectedDepartureTime 76 | expectedArrivalTime 77 | forAlighting 78 | forBoarding 79 | notices { 80 | ...${noticeFields} 81 | } 82 | predictionInaccurate 83 | quay { 84 | ...${quayFields} 85 | } 86 | realtime 87 | requestStop 88 | serviceJourney { 89 | ...${serviceJourneyFields} 90 | } 91 | }` 92 | 93 | export const fragments = uniq([ 94 | fragment, 95 | ...bookingArrangementFragments, 96 | ...noticeFragments, 97 | ...quayFragments, 98 | ...serviceJourneyFragments, 99 | ]) 100 | -------------------------------------------------------------------------------- /src/fields/Interchange.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @deprecated 3 | * The JourneyPlanner v2 queries and types are deprecated. 4 | * Write your own GraphQL queries for JourneyPlanner v3. 5 | * Write your own types or use those from JourneyPlannerTypes where applicable. 6 | */ 7 | export interface Interchange { 8 | guaranteed: boolean 9 | staySeated: boolean 10 | FromServiceJourney?: { 11 | id: string 12 | } 13 | ToServiceJourney?: { 14 | id: string 15 | } 16 | } 17 | 18 | export const fragmentName = 'interchangeFields' 19 | 20 | const fragment = ` 21 | fragment ${fragmentName} on Interchange { 22 | guaranteed 23 | staySeated 24 | FromServiceJourney { 25 | id 26 | } 27 | ToServiceJourney { 28 | id 29 | } 30 | } 31 | ` 32 | 33 | export const fragments = [fragment] 34 | -------------------------------------------------------------------------------- /src/fields/Leg.ts: -------------------------------------------------------------------------------- 1 | import { LegMode, TransportSubmode } from '../types/Mode' 2 | 3 | import { uniq } from '../utils' 4 | 5 | import { 6 | fragmentName as lineFields, 7 | fragments as lineFragments, 8 | Line, 9 | } from './Line' 10 | 11 | import { 12 | fragmentName as placeFields, 13 | fragments as placeFragments, 14 | Place, 15 | } from './Place' 16 | 17 | import { 18 | fragmentName as authorityFields, 19 | fragments as authorityFragments, 20 | Authority, 21 | } from './Authority' 22 | 23 | import { 24 | fragmentName as operatorFields, 25 | fragments as operatorFragments, 26 | Operator, 27 | } from './Operator' 28 | 29 | import { 30 | fragmentName as serviceJourneyFields, 31 | fragments as serviceJourneyFragments, 32 | ServiceJourney, 33 | } from './ServiceJourney' 34 | 35 | import { 36 | fragmentName as situationFields, 37 | fragments as situationFragments, 38 | Situation, 39 | } from './Situation' 40 | 41 | import { 42 | fragmentName as interchangeFields, 43 | fragments as interchangeFragments, 44 | Interchange, 45 | } from './Interchange' 46 | 47 | import { 48 | fragmentName as pointsOnLinkFields, 49 | fragments as pointsOnLinkFragments, 50 | PointsOnLink, 51 | } from './PointsOnLink' 52 | 53 | import { 54 | fragmentName as estimatedCallFields, 55 | fragments as estimatedCallFragments, 56 | EstimatedCall, 57 | IntermediateEstimatedCall, 58 | } from './EstimatedCall' 59 | 60 | import { 61 | fragmentName as bookingArrangementFields, 62 | fragments as bookingArrangementFragments, 63 | BookingArrangement, 64 | } from './BookingArrangement' 65 | 66 | import { Notice } from './Notice' 67 | 68 | /** 69 | * @deprecated 70 | * The JourneyPlanner v2 queries and types are deprecated. 71 | * Write your own GraphQL queries for JourneyPlanner v3. 72 | * Write your own types or use those from JourneyPlannerTypes where applicable. 73 | */ 74 | export interface Leg { 75 | /** The aimed date and time this leg ends. */ 76 | aimedEndTime: string 77 | /** The aimed date and time this leg starts. */ 78 | aimedStartTime: string 79 | /** For ride legs, the service authority used for this legs. For non-ride legs, null. */ 80 | authority?: Authority 81 | /** The distance traveled while traversing the leg in meters. */ 82 | distance: number 83 | /** This sums the direct durations of each leg. Be careful about using this, as it is not equal to the duration between startTime and endTime. See the directDuration documentation on Leg. */ 84 | directDuration: number 85 | /** Duration of the trip, in seconds. */ 86 | duration: number 87 | /** The expected, realtime adjusted date and time this leg ends. */ 88 | expectedEndTime: string 89 | expectedStartTime: string 90 | /** EstimatedCall for the quay where the leg originates. */ 91 | fromEstimatedCall?: EstimatedCall 92 | /** The Place where the leg originates. */ 93 | fromPlace: Place 94 | interchangeFrom?: Interchange 95 | interchangeTo?: Interchange 96 | /** For ride legs, estimated calls for quays between the Place where the leg originates and the Place where the leg ends. For non-ride legs, empty list. */ 97 | intermediateEstimatedCalls: IntermediateEstimatedCall[] 98 | /** For ride legs, the line. For non-ride legs, undefined. */ 99 | line?: Line 100 | /** The mode of transport or access (e.g., foot) used when traversing this leg. */ 101 | mode: LegMode 102 | notices?: Notice[] // from mapper 103 | /** For ride legs, the operator used for this legs. For non-ride legs, null. */ 104 | operator?: Operator 105 | pointsOnLink?: PointsOnLink 106 | /** Whether there is real-time data about this leg or not */ 107 | realtime: boolean 108 | ride: boolean 109 | rentedBike?: boolean 110 | /** For ride legs, the service journey. For non-ride legs, null. */ 111 | serviceJourney: ServiceJourney 112 | /** All relevant situations for this leg */ 113 | situations: Situation[] 114 | /** EstimatedCall for the quay where the leg ends. */ 115 | toEstimatedCall?: EstimatedCall 116 | /** The Place where the leg ends. */ 117 | toPlace: Place 118 | /** The transport sub mode (e.g., localBus or expressBus) used when traversing this leg. Null if leg is not a ride */ 119 | transportSubmode?: TransportSubmode 120 | bookingArrangements?: BookingArrangement 121 | } 122 | 123 | export const fragmentName = 'legFields' 124 | 125 | const fragment = ` 126 | fragment ${fragmentName} on Leg { 127 | aimedEndTime 128 | aimedStartTime 129 | authority { 130 | ...${authorityFields} 131 | } 132 | distance 133 | directDuration 134 | duration 135 | expectedEndTime 136 | expectedStartTime 137 | fromEstimatedCall { 138 | ...${estimatedCallFields} 139 | } 140 | fromPlace { 141 | ...${placeFields} 142 | } 143 | interchangeFrom { 144 | ...${interchangeFields} 145 | } 146 | interchangeTo { 147 | ...${interchangeFields} 148 | } 149 | intermediateEstimatedCalls { 150 | ...${estimatedCallFields} 151 | } 152 | line { 153 | ...${lineFields} 154 | } 155 | mode 156 | operator { 157 | ...${operatorFields} 158 | } 159 | pointsOnLink { 160 | ...${pointsOnLinkFields} 161 | } 162 | realtime 163 | ride 164 | rentedBike 165 | serviceJourney { 166 | ...${serviceJourneyFields} 167 | } 168 | situations { 169 | ...${situationFields} 170 | } 171 | toEstimatedCall { 172 | ...${estimatedCallFields} 173 | } 174 | toPlace { 175 | ...${placeFields} 176 | } 177 | transportSubmode 178 | bookingArrangements { 179 | ...${bookingArrangementFields} 180 | } 181 | } 182 | ` 183 | 184 | export const fragments = uniq([ 185 | fragment, 186 | ...lineFragments, 187 | ...placeFragments, 188 | ...authorityFragments, 189 | ...operatorFragments, 190 | ...serviceJourneyFragments, 191 | ...situationFragments, 192 | ...interchangeFragments, 193 | ...pointsOnLinkFragments, 194 | ...estimatedCallFragments, 195 | ...bookingArrangementFragments, 196 | ]) 197 | -------------------------------------------------------------------------------- /src/fields/Line.ts: -------------------------------------------------------------------------------- 1 | import { TransportMode, TransportSubmode } from '../types/Mode' 2 | 3 | import { uniq } from '../utils' 4 | 5 | import { 6 | fragmentName as bookingArrangementFields, 7 | fragments as bookingArrangementFragments, 8 | BookingArrangement, 9 | } from './BookingArrangement' 10 | 11 | import { 12 | fragmentName as noticeFields, 13 | fragments as noticeFragments, 14 | Notice, 15 | } from './Notice' 16 | 17 | type FlexibleLineType = 18 | | 'corridorService' 19 | | 'mainRouteWithFlexibleEnds' 20 | | 'flexibleAreasOnly' 21 | | 'hailAndRideSections' 22 | | 'fixedStopAreaWide' 23 | | 'freeAreaAreaWide' 24 | | 'mixedFlexible' 25 | | 'mixedFlexibleAndFixed' 26 | | 'fixed' 27 | | 'other' 28 | 29 | /** 30 | * @deprecated 31 | * The JourneyPlanner v2 queries and types are deprecated. 32 | * Write your own GraphQL queries for JourneyPlanner v3. 33 | * Write your own types or use those from JourneyPlannerTypes where applicable. 34 | */ 35 | export type Line = { 36 | /** @deprecated Use bookingArrangements on Leg or ServiceJourney instead. */ 37 | bookingArrangements?: BookingArrangement 38 | description?: string 39 | flexibleLineType?: FlexibleLineType 40 | id: string 41 | name: string 42 | notices: Notice[] 43 | publicCode: string 44 | transportMode: TransportMode 45 | transportSubmode: TransportSubmode 46 | } 47 | 48 | export const fragmentName = 'lineFields' 49 | 50 | export const fragment = ` 51 | fragment ${fragmentName} on Line { 52 | bookingArrangements { 53 | ...${bookingArrangementFields} 54 | } 55 | description 56 | flexibleLineType 57 | id 58 | name 59 | notices { 60 | ...${noticeFields} 61 | } 62 | publicCode 63 | transportMode 64 | transportSubmode 65 | } 66 | ` 67 | 68 | export const fragments = uniq([ 69 | fragment, 70 | ...bookingArrangementFragments, 71 | ...noticeFragments, 72 | ]) 73 | -------------------------------------------------------------------------------- /src/fields/Notice.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @deprecated 3 | * The JourneyPlanner v2 queries and types are deprecated. 4 | * Write your own GraphQL queries for JourneyPlanner v3. 5 | * Write your own types or use those from JourneyPlannerTypes where applicable. 6 | */ 7 | export interface Notice { 8 | text: string 9 | } 10 | 11 | export const fragmentName = 'noticeFields' 12 | 13 | const fragment = ` 14 | fragment ${fragmentName} on Notice { 15 | text 16 | } 17 | ` 18 | 19 | export const fragments = [fragment] 20 | -------------------------------------------------------------------------------- /src/fields/Operator.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @deprecated 3 | * The JourneyPlanner v2 queries and types are deprecated. 4 | * Write your own GraphQL queries for JourneyPlanner v3. 5 | * Write your own types or use those from JourneyPlannerTypes where applicable. 6 | */ 7 | export interface Operator { 8 | id: string 9 | name: string 10 | url?: string 11 | } 12 | 13 | export const fragmentName = 'operatorFields' 14 | 15 | const fragment = ` 16 | fragment ${fragmentName} on Operator { 17 | id 18 | name 19 | url 20 | } 21 | ` 22 | 23 | export const fragments = [fragment] 24 | -------------------------------------------------------------------------------- /src/fields/Place.ts: -------------------------------------------------------------------------------- 1 | import { uniq } from '../utils' 2 | 3 | import { 4 | fragmentName as quayFields, 5 | fragments as quayFragments, 6 | Quay, 7 | } from './Quay' 8 | 9 | import { 10 | fragmentName as bikeRentalStationFields, 11 | fragments as bikeRentalStationFragments, 12 | BikeRentalStation, 13 | } from './BikeRentalStation' 14 | 15 | /** 16 | * @deprecated 17 | * The JourneyPlanner v2 queries and types are deprecated. 18 | * Write your own GraphQL queries for JourneyPlanner v3. 19 | * Write your own types or use those from JourneyPlannerTypes where applicable. 20 | */ 21 | export interface Place { 22 | latitude: number 23 | longitude: number 24 | name: string 25 | quay?: Quay 26 | bikeRentalStation?: BikeRentalStation 27 | } 28 | 29 | export const fragmentName = 'placeFields' 30 | 31 | export const fragment = ` 32 | fragment ${fragmentName} on Place { 33 | name 34 | latitude 35 | longitude 36 | quay { 37 | ...${quayFields} 38 | } 39 | bikeRentalStation { 40 | ...${bikeRentalStationFields} 41 | } 42 | }` 43 | 44 | export const fragments = uniq([ 45 | fragment, 46 | ...quayFragments, 47 | ...bikeRentalStationFragments, 48 | ]) 49 | -------------------------------------------------------------------------------- /src/fields/PointsOnLink.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @deprecated 3 | * The JourneyPlanner v2 queries and types are deprecated. 4 | * Write your own GraphQL queries for JourneyPlanner v3. 5 | * Write your own types or use those from JourneyPlannerTypes where applicable. 6 | */ 7 | export type PointsOnLink = { 8 | points: string 9 | length: number 10 | } 11 | 12 | export const fragmentName = 'pointsOnLinkFields' 13 | 14 | const fragment = ` 15 | fragment ${fragmentName} on PointsOnLink { 16 | points 17 | length 18 | } 19 | ` 20 | 21 | export const fragments = [fragment] 22 | -------------------------------------------------------------------------------- /src/fields/Quay.ts: -------------------------------------------------------------------------------- 1 | import { uniq } from '../utils' 2 | 3 | import { 4 | fragmentName as situationFields, 5 | fragments as situationFragments, 6 | Situation, 7 | } from './Situation' 8 | 9 | import { 10 | fragmentName as stopPlaceFields, 11 | fragments as stopPlaceFragments, 12 | StopPlace, 13 | } from './StopPlace' 14 | 15 | /** 16 | * @deprecated 17 | * The JourneyPlanner v2 queries and types are deprecated. 18 | * Write your own GraphQL queries for JourneyPlanner v3. 19 | * Write your own types or use those from JourneyPlannerTypes where applicable. 20 | */ 21 | export interface Quay { 22 | id: string 23 | name: string 24 | description: string 25 | publicCode: string 26 | situations: Situation[] 27 | stopPlace: StopPlace 28 | } 29 | 30 | export const fragmentName = 'quayFields' 31 | 32 | export const fragment = ` 33 | fragment ${fragmentName} on Quay { 34 | id 35 | name 36 | description 37 | publicCode 38 | situations { 39 | ...${situationFields} 40 | } 41 | stopPlace { 42 | ...${stopPlaceFields} 43 | } 44 | } 45 | ` 46 | 47 | export const fragments = uniq([ 48 | fragment, 49 | ...situationFragments, 50 | ...stopPlaceFragments, 51 | ]) 52 | -------------------------------------------------------------------------------- /src/fields/ServiceJourney.ts: -------------------------------------------------------------------------------- 1 | import { uniq } from '../utils' 2 | 3 | import { TransportSubmode } from '../types/Mode' 4 | 5 | import { 6 | fragmentName as noticeFields, 7 | fragments as noticeFragments, 8 | Notice, 9 | } from './Notice' 10 | 11 | import { 12 | fragmentName as lineFields, 13 | fragments as lineFragments, 14 | Line, 15 | } from './Line' 16 | 17 | import { 18 | fragmentName as bookingArrangementFields, 19 | fragments as bookingArrangementFragments, 20 | BookingArrangement, 21 | } from './BookingArrangement' 22 | 23 | interface JourneyPattern { 24 | line: Line 25 | notices?: Notice[] 26 | } 27 | 28 | /** 29 | * @deprecated 30 | * The JourneyPlanner v2 queries and types are deprecated. 31 | * Write your own GraphQL queries for JourneyPlanner v3. 32 | * Write your own types or use those from JourneyPlannerTypes where applicable. 33 | */ 34 | export interface ServiceJourney { 35 | id: string 36 | bookingArrangements?: BookingArrangement 37 | journeyPattern?: JourneyPattern 38 | notices?: Notice[] 39 | publicCode?: string 40 | privateCode?: string 41 | transportSubmode?: TransportSubmode 42 | } 43 | 44 | export const fragmentName = 'serviceJourneyFields' 45 | 46 | export const fragment = ` 47 | fragment ${fragmentName} on ServiceJourney { 48 | id 49 | bookingArrangements { 50 | ...${bookingArrangementFields} 51 | } 52 | journeyPattern { 53 | line { 54 | ...${lineFields} 55 | } 56 | notices { 57 | ...${noticeFields} 58 | } 59 | } 60 | notices { 61 | ...${noticeFields} 62 | } 63 | publicCode 64 | privateCode 65 | transportSubmode 66 | } 67 | ` 68 | 69 | export const fragments = uniq([ 70 | fragment, 71 | ...bookingArrangementFragments, 72 | ...noticeFragments, 73 | ...lineFragments, 74 | ]) 75 | -------------------------------------------------------------------------------- /src/fields/Situation.ts: -------------------------------------------------------------------------------- 1 | import { MultilingualString } from '../types/MultilingualString' 2 | 3 | import { uniq } from '../utils' 4 | 5 | import { 6 | fragmentName as lineFields, 7 | fragments as lineFragments, 8 | Line, 9 | } from './Line' 10 | 11 | /** 12 | * @deprecated 13 | * The JourneyPlanner v2 queries and types are deprecated. 14 | * Write your own GraphQL queries for JourneyPlanner v3. 15 | * Write your own types or use those from JourneyPlannerTypes where applicable. 16 | */ 17 | export type ReportType = 'general' | 'incident' | null 18 | 19 | /** 20 | * @deprecated 21 | * The JourneyPlanner v2 queries and types are deprecated. 22 | * Write your own GraphQL queries for JourneyPlanner v3. 23 | * Write your own types or use those from JourneyPlannerTypes where applicable. 24 | */ 25 | export interface ValidityPeriod { 26 | startTime: string 27 | endTime: string 28 | } 29 | 30 | /** 31 | * @deprecated 32 | * The JourneyPlanner v2 queries and types are deprecated. 33 | * Write your own GraphQL queries for JourneyPlanner v3. 34 | * Write your own types or use those from JourneyPlannerTypes where applicable. 35 | */ 36 | export interface InfoLink { 37 | uri: string 38 | label: string 39 | } 40 | 41 | /** 42 | * @deprecated 43 | * The JourneyPlanner v2 queries and types are deprecated. 44 | * Write your own GraphQL queries for JourneyPlanner v3. 45 | * Write your own types or use those from JourneyPlannerTypes where applicable. 46 | */ 47 | export interface Situation { 48 | situationNumber: string 49 | summary: MultilingualString[] 50 | description: MultilingualString[] 51 | advice: MultilingualString[] 52 | /** 53 | * @deprecated lines will be removed from Situation in a future major version. 54 | */ 55 | lines?: Line[] 56 | validityPeriod: ValidityPeriod 57 | reportType: ReportType 58 | infoLinks: InfoLink[] 59 | } 60 | 61 | export const fragmentName = 'situationFields' 62 | 63 | const fragment = ` 64 | fragment ${fragmentName} on PtSituationElement { 65 | situationNumber 66 | summary { 67 | language 68 | value 69 | } 70 | description { 71 | language 72 | value 73 | } 74 | advice { 75 | language 76 | value 77 | } 78 | lines { 79 | ...${lineFields} 80 | } 81 | validityPeriod { 82 | startTime 83 | endTime 84 | } 85 | reportType 86 | infoLinks { 87 | uri 88 | label 89 | } 90 | } 91 | ` 92 | 93 | export const fragments = uniq([fragment, ...lineFragments]) 94 | -------------------------------------------------------------------------------- /src/fields/StopPlace.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @deprecated 3 | * The JourneyPlanner v2 queries and types are deprecated. 4 | * Write your own GraphQL queries for JourneyPlanner v3. 5 | * Write your own types or use those from JourneyPlannerTypes where applicable. 6 | */ 7 | export interface StopPlace { 8 | id: string 9 | description?: string 10 | name: string 11 | latitude?: number 12 | longitude?: number 13 | tariffZones?: Array<{ 14 | id: string 15 | }> 16 | } 17 | 18 | export const fragmentName = 'stopPlaceFields' 19 | 20 | const fragment = ` 21 | fragment ${fragmentName} on StopPlace { 22 | id 23 | description 24 | name 25 | latitude 26 | longitude 27 | tariffZones { 28 | id 29 | } 30 | } 31 | ` 32 | 33 | export const fragments = [fragment] 34 | -------------------------------------------------------------------------------- /src/geocoder/autocomplete/index.ts: -------------------------------------------------------------------------------- 1 | import { FeatureCollection, Point } from 'geojson' 2 | 3 | import { ArgumentConfig, getServiceConfig, getGeocoderHost } from '../../config' 4 | import { get, RequestOptions } from '../../http' 5 | 6 | import { 7 | stringifyCommaSeparatedList, 8 | transformBoundaryParam, 9 | transformFocusParam, 10 | } from '../helper' 11 | import { Boundary, Focus, Location } from '../types' 12 | 13 | export interface AutocompleteParams { 14 | /** 15 | * The search query to find matching locations for. 16 | * 17 | * @example "Oslo S" 18 | */ 19 | text: string 20 | /** 21 | * You can get search results in another language, if available, by 22 | * specifying a target language code with your request following the 23 | * BCP47 standard (https://www.rfc-editor.org/rfc/bcp/bcp47.txt). 24 | * 25 | * By default, search responses are in the default locale of the dataset. 26 | * However, if you include a language code, the search attempts to return 27 | * place names in the language you specified. 28 | * 29 | * If the language you requested is unavailable, then the default language 30 | * is returned. In some cases, this is the local dialect, or it may be 31 | * English for other datasets. 32 | * 33 | * @defaultValue "no" 34 | */ 35 | lang?: string 36 | /** 37 | * To focus your search based upon a geographical area, such as the center 38 | * of the user's map or at the device's GPS location, supply a focus point. 39 | * This boosts locally relevant results higher. 40 | */ 41 | focus?: Focus 42 | /** 43 | * You can set a boundary to filter results by a geographical region. 44 | */ 45 | boundary?: Boundary 46 | sources?: string[] 47 | /** 48 | * The type of record is referred to as its layer. Due to limitations in 49 | * Pelias the definitions of layers has been re-defined and limited to the 50 | * following two layers: 51 | * 52 | * - venue: Stops 53 | * - address: POI, streets, addresses, stop groups 54 | */ 55 | layers?: Array<'address' | 'venue'> 56 | /** 57 | * Controls whether the search returns multimodal stops, child stops of the 58 | * multimodal stops, or both. Does not affect monomodal stops. 59 | * 60 | * @defaultValue 'parent' 61 | */ 62 | multiModal?: 'parent' | 'child' | 'all' 63 | /** 64 | * Governs the maximum number of results. Valid values are from 1 to 100 inclusive. 65 | */ 66 | size?: number 67 | tariffZoneAuthorities?: string[] 68 | tariffZoneIds?: string[] 69 | } 70 | 71 | export default function createAutocomplete(argConfig: ArgumentConfig) { 72 | const config = getServiceConfig(argConfig) 73 | 74 | return async function autocomplete( 75 | params: AutocompleteParams, 76 | options?: RequestOptions, 77 | ): Promise> { 78 | const { host, headers } = getGeocoderHost(config) 79 | const { 80 | sources, 81 | layers, 82 | lang, 83 | boundary, 84 | focus, 85 | tariffZoneAuthorities, 86 | tariffZoneIds, 87 | ...rest 88 | } = params 89 | 90 | const searchParams = { 91 | ...rest, 92 | lang: lang || 'no', 93 | sources: stringifyCommaSeparatedList(sources), 94 | layers: stringifyCommaSeparatedList(layers), 95 | tariff_zone_authorities: stringifyCommaSeparatedList( 96 | tariffZoneAuthorities, 97 | ), 98 | tariff_zone_ids: stringifyCommaSeparatedList(tariffZoneIds), 99 | ...transformBoundaryParam(boundary), 100 | ...transformFocusParam(focus), 101 | } 102 | 103 | const url = `${host}/autocomplete` 104 | const data = await get>( 105 | url, 106 | searchParams, 107 | headers, 108 | config.fetch, 109 | options, 110 | ) 111 | 112 | return data 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/geocoder/helper.ts: -------------------------------------------------------------------------------- 1 | import { Boundary, Focus } from './types' 2 | 3 | export function stringifyCommaSeparatedList( 4 | value: string | string[] | undefined, 5 | ): string | undefined { 6 | if (!value) return undefined 7 | if (typeof value === 'string') return value 8 | return value.join(',') 9 | } 10 | 11 | interface BoundaryApi { 12 | 'boundary.rect.min_lon'?: number 13 | 'boundary.rect.max_lon'?: number 14 | 'boundary.rect.min_lat'?: number 15 | 'boundary.rect.max_lat'?: number 16 | 'boundary.country'?: string 17 | 'boundary.county_ids'?: string 18 | 'boundary.locality_ids'?: string 19 | 'boundary.circle.lat'?: number 20 | 'boundary.circle.lon'?: number 21 | 'boundary.circle.radius'?: number 22 | } 23 | 24 | export function transformBoundaryParam(boundary?: Boundary): BoundaryApi { 25 | if (!boundary) return {} 26 | 27 | let result: BoundaryApi = { 28 | 'boundary.country': boundary.country, 29 | 'boundary.county_ids': stringifyCommaSeparatedList(boundary.countyIds), 30 | 'boundary.locality_ids': stringifyCommaSeparatedList( 31 | boundary.localityIds, 32 | ), 33 | } 34 | 35 | if (boundary.rect) { 36 | result = { 37 | ...result, 38 | 'boundary.rect.min_lat': boundary.rect.minLat, 39 | 'boundary.rect.min_lon': boundary.rect.minLon, 40 | 'boundary.rect.max_lat': boundary.rect.maxLat, 41 | 'boundary.rect.max_lon': boundary.rect.maxLon, 42 | } 43 | } 44 | 45 | const radius = boundary?.circle?.radius 46 | 47 | if (typeof radius === 'number') { 48 | result = { 49 | ...result, 50 | 'boundary.circle.radius': radius, 51 | } 52 | 53 | if (boundary?.circle?.lat && boundary?.circle?.lon) { 54 | result = { 55 | ...result, 56 | 'boundary.circle.lat': boundary.circle.lat, 57 | 'boundary.circle.lon': boundary.circle.lon, 58 | } 59 | } 60 | } 61 | 62 | return result 63 | } 64 | 65 | interface FocusApi { 66 | 'focus.point.lat': number 67 | 'focus.point.lon': number 68 | 'focus.weight'?: number 69 | 'focus.function'?: 'linear' | 'exp' 70 | 'focus.scale'?: number 71 | } 72 | 73 | export function transformFocusParam(focus?: Focus): FocusApi | undefined { 74 | if (!focus) return 75 | 76 | return { 77 | 'focus.point.lat': focus.point.lat, 78 | 'focus.point.lon': focus.point.lon, 79 | 'focus.weight': focus.weight, 80 | 'focus.function': focus.function, 81 | 'focus.scale': focus.scale, 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/geocoder/index.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentConfig } from '../config' 2 | 3 | import { default as createAutocomplete } from './autocomplete' 4 | import { default as createReverse } from './reverse' 5 | 6 | export interface GeocoderClient { 7 | /** 8 | * Search for features using a search string. This is useful for 9 | * autosuggest search fields. 10 | * 11 | * If you are building an end-user application, you can use the 12 | * `autocomplete` method to enable real-time feedback. This type-ahead 13 | * functionality helps users find what they are looking for, without 14 | * requiring them to fully specify their search term. Typically, the user 15 | * starts typing and a drop-down list appears where they can choose the 16 | * term from the list below. 17 | * 18 | * Please throttle or debounce your calls to avoid bursts of requests. 19 | * 20 | * @example Searching for locations with name similar to "Oslo S" 21 | * 22 | * ```typescript 23 | * import { Feature, Point } from 'geojson' 24 | * import createEnturService, { GeocoderTypes } from '@entur/sdk' 25 | * // or: const createEnturService = require('@entur/sdk').default 26 | * 27 | * const service = createEnturService({ 28 | * clientName: 'awesomecompany-awesomeapp', 29 | * }) 30 | * 31 | * async function example() { 32 | * try { 33 | * const featureCollection = await service.geocoder.autocomplete({ 34 | * text: 'Oslo S', 35 | * }) 36 | * 37 | * const features: Array> = 38 | * featureCollection.features 39 | * 40 | * console.log(features) 41 | * } catch (error) { 42 | * console.error(error) 43 | * } 44 | * } 45 | * 46 | * example() 47 | * ``` 48 | */ 49 | autocomplete: ReturnType 50 | /** 51 | * Find features within a geographical area, defined by a coordinate and a radius. 52 | * 53 | * Reverse geocoding is used for finding places or addresses near a 54 | * latitude, longitude pair—like clicking on a map to see what's there when 55 | * the map doesn't show it otherwise. For example, picture a map showing 56 | * building outlines but no labels, then clicking on a building and being 57 | * shown the name of the business. That's reverse geocoding. 58 | * 59 | * @example Finding locations within 2 km of Oslo City Hall (Rådhuset). 60 | * 61 | * ```typescript 62 | * import { Feature, Point } from 'geojson' 63 | * import createEnturService, { GeocoderTypes } from '@entur/sdk' 64 | * // or: const createEnturService = require('@entur/sdk').default 65 | * 66 | * const service = createEnturService({ 67 | * clientName: 'awesomecompany-awesomeapp', 68 | * }) 69 | * 70 | * async function example() { 71 | * try { 72 | * const featureCollection = await service.geocoder.reverse({ 73 | * point: { 74 | * lat: 59.9127992, 75 | * lon: 10.7344255, 76 | * }, 77 | * boundary: { 78 | * circle: { 79 | * radius: 2 80 | * } 81 | * } 82 | * }) 83 | * 84 | * const features: Array> = 85 | * featureCollection.features 86 | * 87 | * console.log(features) 88 | * } catch (error) { 89 | * console.error(error) 90 | * } 91 | * } 92 | * 93 | * example() 94 | * ``` 95 | */ 96 | reverse: ReturnType 97 | } 98 | 99 | export default function createClient(config: ArgumentConfig): GeocoderClient { 100 | return { 101 | autocomplete: createAutocomplete(config), 102 | reverse: createReverse(config), 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/geocoder/reverse/index.ts: -------------------------------------------------------------------------------- 1 | import { FeatureCollection, Point } from 'geojson' 2 | 3 | import { ArgumentConfig, getServiceConfig, getGeocoderHost } from '../../config' 4 | import { get, RequestOptions } from '../../http' 5 | 6 | import { stringifyCommaSeparatedList, transformBoundaryParam } from '../helper' 7 | import { Boundary, Location } from '../types' 8 | 9 | export interface ReverseParams { 10 | point: { 11 | lat: number 12 | lon: number 13 | } 14 | /** 15 | * You can get search results in another language, if available, by 16 | * specifying a target language code with your request following the 17 | * BCP47 standard (https://www.rfc-editor.org/rfc/bcp/bcp47.txt). 18 | * 19 | * By default, search responses are in the default locale of the dataset. 20 | * However, if you include a language code, the search attempts to return 21 | * place names in the language you specified. 22 | * 23 | * If the language you requested is unavailable, then the default language 24 | * is returned. In some cases, this is the local dialect, or it may be 25 | * English for other datasets. 26 | * 27 | * @defaultValue "no" 28 | */ 29 | lang?: string 30 | /** 31 | * You can set a boundary to filter results by a geographical region. 32 | */ 33 | boundary?: Boundary 34 | sources?: string[] 35 | /** 36 | * The type of record is referred to as its layer. Due to limitations in 37 | * Pelias the definitions of layers has been re-defined and limited to the 38 | * following two layers: 39 | * 40 | * - venue: Stops 41 | * - address: POI, streets, addresses, stop groups 42 | */ 43 | layers?: Array<'address' | 'venue'> 44 | /** 45 | * Governs the maximum number of results. Valid values 1 - 100 inclusive. 46 | */ 47 | size?: number 48 | } 49 | 50 | export default function createReverse(argConfig: ArgumentConfig) { 51 | const config = getServiceConfig(argConfig) 52 | 53 | return async function reverse( 54 | params: ReverseParams, 55 | options?: RequestOptions, 56 | ): Promise> { 57 | const { host, headers } = getGeocoderHost(config) 58 | const { point, sources, layers, lang, boundary, size } = params 59 | 60 | if (typeof point !== 'object') { 61 | throw new TypeError( 62 | `geocoder.reverse expects argument \`point\` to be of type object, but got ${typeof point}.`, 63 | ) 64 | } 65 | 66 | if (typeof point.lat !== 'number') { 67 | throw new TypeError( 68 | `geocoder.reverse expects \`point.lat\` to be of type number, but got ${typeof point.lat}.`, 69 | ) 70 | } 71 | 72 | if (typeof point.lon !== 'number') { 73 | throw new TypeError( 74 | `geocoder.reverse expects \`point.lon\` to be of type number, but got ${typeof point.lon}.`, 75 | ) 76 | } 77 | 78 | const searchParams = { 79 | 'point.lat': point.lat, 80 | 'point.lon': point.lon, 81 | lang: lang || 'no', 82 | sources: stringifyCommaSeparatedList(sources), 83 | layers: stringifyCommaSeparatedList(layers), 84 | size, 85 | ...transformBoundaryParam(boundary), 86 | } 87 | 88 | const url = `${host}/reverse` 89 | const data = await get>( 90 | url, 91 | searchParams, 92 | headers, 93 | config.fetch, 94 | options, 95 | ) 96 | 97 | return data 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/geocoder/types.ts: -------------------------------------------------------------------------------- 1 | export type { AutocompleteParams } from './autocomplete' 2 | export type { ReverseParams } from './reverse' 3 | 4 | export interface Location { 5 | /** 6 | * The accuracy field gives information on the accuracy of the latitude/longitude point returned with the given result. This value is a property of the result itself and won't change based on the query. 7 | * There are currently two possible values for the accuracy field: point and centroid. 8 | * 9 | * `point` results are generally addresses, venues, or interpolated addresses. A point result means the record represents a record that can reasonably be represented by a single latitude/longitude point. 10 | * `centroid` results, on the other hand, are records that represent a larger area, such as a city or country. Pelias cannot currently return results with geometries made of polygons or lines, so all such records are estimated with a centroid. 11 | */ 12 | accuracy?: 'point' | 'centroid' 13 | /** 14 | * A local administrative boundary 15 | */ 16 | borough: string 17 | borough_gid: string 18 | /** 19 | * A sub-type within layers. 20 | */ 21 | category: string[] 22 | /** 23 | * Country abbreviation as ISO 3166-1 alpha-3 code. 24 | */ 25 | country_a: string 26 | country_gid: string 27 | /** 28 | * Name of county (fylke) 29 | * 30 | * @example "Nordland" 31 | */ 32 | county: string 33 | /** 34 | * ID of county (prefixed with "whosonfirst:county") 35 | * 36 | * @example "whosonfirst:county:KVE:TopographicPlace:18" 37 | */ 38 | county_gid: string 39 | /** 40 | * This is a general score computed to calculate how likely result is what was asked for. It's meant to be a combination of all the information available to Pelias. It's not super sophisticated, and results may not be sorted in confidence-score order. In that case results returned first should be trusted more. Confidence scores are floating point numbers ranging from 0.0 to 1.0. 41 | * Confidence scores are calculated differently for different endpoints: 42 | * For reverse geocoding it's based on distance from the reverse geocoded point. The progression of confidence scores is as follows: 43 | * distance confidence score 44 | * less than 1 meter 1.0 45 | * 1 - 10 meters 0.9 46 | * 11 - 100 meters 0.8 47 | * 101 - 250 meters 0.7 48 | * 251 - 1000 meters 0.6 49 | */ 50 | confidence?: number 51 | /** 52 | * For reverse geocoding: The feature's distance in kilometers from the circle's midpoint. 53 | */ 54 | distance?: number 55 | gid: string 56 | housenumber?: string 57 | id: string 58 | /** 59 | * The label is a human-friendly representation of the place, with the most 60 | * complete details, that is ready to be displayed to an end user. 61 | * Examples of a label include a business or venue name with its locality, 62 | * a complete mailing address, or a locality with region and country names. 63 | * The label field attempts to use a format that is right for the region of 64 | * the result. 65 | */ 66 | label?: string 67 | /** 68 | * The type of record is referred to as its layer. Due to limitations in 69 | * Pelias the definitions of layers has been re-defined and limited to the 70 | * following two layers: 71 | * 72 | * - venue: Stops 73 | * - address: POI, streets, addresses, stop groups 74 | */ 75 | layer: 'venue' | 'address' 76 | /** 77 | * Name of municipality (kommune) 78 | * 79 | * @example "Oslo" 80 | */ 81 | locality: string 82 | /** 83 | * ID of county (prefixed with "whosonfirst:locality") 84 | * 85 | * @example "whosonfirst:locality:KVE:TopographicPlace:0301" 86 | */ 87 | locality_gid: string 88 | /** 89 | * The name is a short description of the location, such as a 90 | * business name, a locality name, or part of an address, depending 91 | * on what is being searched for and what is returned. 92 | * 93 | * For address searches, the housenumber and street properties are brought 94 | * together under the name property in the local standard format. 95 | * This saves you from having to reassemble the address yourself, including 96 | * to determine whether the numbers should be placed before or after the 97 | * street name. 98 | */ 99 | name: string 100 | postalcode: string 101 | source: string 102 | source_id: string 103 | street: string 104 | tariff_zones?: string[] 105 | } 106 | 107 | export interface Boundary { 108 | /** 109 | * A bounding box to get features within. Features that are outside this box will not be returned. 110 | * Only supported for autocomplete, not reverse. 111 | */ 112 | rect?: { 113 | minLat: number 114 | minLon: number 115 | maxLat: number 116 | maxLon: number 117 | } 118 | /** 119 | * ISO 3166-1 alpha-3 country code County code (ISO 3166-1 alpha-3) (https://no.wikipedia.org/wiki/ISO_3166-1_alfa-3) 120 | */ 121 | country?: string 122 | /** 123 | * Norwegian county numbers are with prefix "KVE:TopographicPlace:" 124 | * https://register.geonorge.no/sosi-kodelister/fylkesnummer-alle 125 | * 126 | * Swedish county numbers are with prefix "LAN:TopographicPlace" 127 | * https://www.scb.se/en/finding-statistics/regional-statistics/regional-divisions/counties-and-municipalities/counties-and-municipalities-in-numerical-order/ 128 | */ 129 | countyIds?: string[] 130 | localityIds?: string[] 131 | /** 132 | * A bounding circle to get features within. Features that are outside this circle will not be returned. 133 | * Only supported for reverse, not autocomplete. 134 | */ 135 | circle?: { 136 | /** 137 | * The latitude of the circle midpoint. 138 | */ 139 | lat?: number 140 | /** 141 | * The longitude of the circle midpoint. 142 | */ 143 | lon?: number 144 | /** 145 | * The radius of the circle in kilometers. 146 | */ 147 | radius: number 148 | } 149 | } 150 | 151 | /** 152 | * To focus your search based upon a geographical area, such as the center of 153 | * the user's map or at the device's GPS location, supply the parameters 154 | * focus.point.lat and focus.point.lon. 155 | * This boosts locally relevant results higher. 156 | */ 157 | export interface Focus { 158 | point: { 159 | lat: number 160 | lon: number 161 | } 162 | /** 163 | * Base weight to be applied to boosting results based on location. 164 | * This value will be multiplied by a factor determined by decay 165 | * function and scale. 166 | * 167 | * @defaultValue 15 168 | */ 169 | weight?: number 170 | /** 171 | * Which decay function to apply. 172 | * 173 | * @defaultValue linear 174 | */ 175 | function?: 'linear' | 'exp' 176 | /** 177 | * Controls the rate of decay, i.e. at which distance in km from the 178 | * given location the scoring will be given the boost factor of the default 179 | * decay value, which is 0.5. 180 | * 181 | * @defaultValue 2500 182 | */ 183 | scale?: number 184 | } 185 | -------------------------------------------------------------------------------- /src/geocoderLegacy/countyIds.ts: -------------------------------------------------------------------------------- 1 | export enum County { 2 | // Norway: 3 | Oslo = 'KVE:TopographicPlace:03', 4 | Rogaland = 'KVE:TopographicPlace:11', 5 | MoreOgRomsdal = 'KVE:TopographicPlace:15', 6 | Nordland = 'KVE:TopographicPlace:18', 7 | Svalbard = 'KVE:TopographicPlace:21', 8 | JanMayen = 'KVE:TopographicPlace:22', 9 | Viken = 'KVE:TopographicPlace:30', 10 | Innlandet = 'KVE:TopographicPlace:34', 11 | VestfoldOgTelemark = 'KVE:TopographicPlace:38', 12 | Agder = 'KVE:TopographicPlace:42', 13 | Vestland = 'KVE:TopographicPlace:46', 14 | Trondelag = 'KVE:TopographicPlace:50', 15 | TromsOgFinnmark = 'KVE:TopographicPlace:54', 16 | 17 | // Sweden: 18 | StockholmsLan = 'LAN:TopographicPlace:01', 19 | UppsalaLan = 'LAN:TopographicPlace:03', 20 | SodermanlandsLan = 'LAN:TopographicPlace:04', 21 | OstergotlandsLan = 'LAN:TopographicPlace:05', 22 | JonkopingsLan = 'LAN:TopographicPlace:06', 23 | KronobergsLan = 'LAN:TopographicPlace:07', 24 | KalmarLan = 'LAN:TopographicPlace:08', 25 | GotlandsLan = 'LAN:TopographicPlace:09', 26 | BlekingeLan = 'LAN:TopographicPlace:10', 27 | SkaneLan = 'LAN:TopographicPlace:12', 28 | HallandsLan = 'LAN:TopographicPlace:13', 29 | VastraGotalandsLan = 'LAN:TopographicPlace:14', 30 | VarmlandsLan = 'LAN:TopographicPlace:17', 31 | OrebroLan = 'LAN:TopographicPlace:18', 32 | VastmanlandsLan = 'LAN:TopographicPlace:19', 33 | DalarnasLan = 'LAN:TopographicPlace:20', 34 | GavleborgsLan = 'LAN:TopographicPlace:21', 35 | VasternorrlandsLan = 'LAN:TopographicPlace:22', 36 | JamtlandsLan = 'LAN:TopographicPlace:23', 37 | VasterbottensLan = 'LAN:TopographicPlace:24', 38 | NorrbottensLan = 'LAN:TopographicPlace:25', 39 | } 40 | -------------------------------------------------------------------------------- /src/geocoderLegacy/index.ts: -------------------------------------------------------------------------------- 1 | import { get, RequestOptions } from '../http' 2 | import { getGeocoderHost, getServiceConfig, ArgumentConfig } from '../config' 3 | import { Feature } from '../types/Feature' 4 | import { Coordinates } from '../types/Coordinates' 5 | import { County } from './countyIds' 6 | 7 | interface PositionParam { 8 | 'focus.point.lat': number 9 | 'focus.point.lon': number 10 | } 11 | 12 | function getPositionParamsFromGeolocationResult( 13 | coords?: Coordinates, 14 | ): PositionParam | undefined { 15 | if (!coords) { 16 | return 17 | } 18 | 19 | const { latitude, longitude } = coords 20 | return { 21 | 'focus.point.lat': latitude, 22 | 'focus.point.lon': longitude, 23 | } 24 | } 25 | 26 | interface Boundary { 27 | rect?: { 28 | minLat: number 29 | minLon: number 30 | maxLat: number 31 | maxLon: number 32 | } 33 | country?: string 34 | countyIds?: County[] 35 | localityIds?: string[] 36 | } 37 | 38 | interface BoundaryApi { 39 | 'boundary.rect.min_lon'?: number 40 | 'boundary.rect.max_lon'?: number 41 | 'boundary.rect.min_lat'?: number 42 | 'boundary.rect.max_lat'?: number 43 | 'boundary.country'?: string 44 | 'boundary.county_ids'?: string 45 | 'boundary.locality_ids'?: string 46 | } 47 | 48 | interface FocusApi { 49 | 'focus.weight'?: number 50 | 'focus.function'?: 'linear' | 'exp' 51 | 'focus.scale'?: number 52 | } 53 | 54 | export interface GetFeaturesParams { 55 | /** @deprecated Use boundary object instead */ 56 | 'boundary.rect.min_lon'?: number 57 | /** @deprecated Use boundary object instead */ 58 | 'boundary.rect.max_lon'?: number 59 | /** @deprecated Use boundary object instead */ 60 | 'boundary.rect.min_lat'?: number 61 | /** @deprecated Use boundary object instead */ 62 | 'boundary.rect.max_lat'?: number 63 | /** @deprecated Use boundary object instead */ 64 | 'boundary.country'?: string 65 | /** @deprecated Use boundary object instead */ 66 | 'boundary.county_ids'?: string 67 | /** @deprecated Use boundary object instead */ 68 | 'boundary.locality_ids'?: string 69 | boundary?: { 70 | rect?: { 71 | minLat: number 72 | minLon: number 73 | maxLat: number 74 | maxLon: number 75 | } 76 | country?: string 77 | countyIds?: County[] 78 | localityIds?: string[] 79 | } 80 | focus?: { 81 | /** 82 | * Base weight to be applied to boosting results based on location. This value will be multiplied by a factor determined by decay function and scale. 83 | * 84 | * @defaultValue 15 85 | */ 86 | weight?: number 87 | /** 88 | * Which decay function to apply. 89 | * 90 | * @defaultValue linear 91 | */ 92 | function?: 'linear' | 'exp' 93 | /** 94 | * Controls the rate of decay, i.e. at which distance in km from the given location the scoring will be given the boost factor of the default decay value, which is 0.5. 95 | * 96 | * @defaultValue 2500 97 | */ 98 | scale?: number 99 | } 100 | multiModal?: 'parent' | 'child' | 'all' 101 | sources?: string[] 102 | layers?: string[] 103 | limit?: number 104 | } 105 | 106 | function stringifyCommaSeparatedList( 107 | value: string | string[] | undefined, 108 | ): string | undefined { 109 | if (!value) return undefined 110 | if (typeof value === 'string') return value 111 | return value.join(',') 112 | } 113 | 114 | function transformBoundaryParam(boundary?: Boundary): BoundaryApi { 115 | if (!boundary) return {} 116 | 117 | let result: BoundaryApi = { 118 | 'boundary.country': boundary.country, 119 | 'boundary.county_ids': stringifyCommaSeparatedList(boundary.countyIds), 120 | 'boundary.locality_ids': stringifyCommaSeparatedList( 121 | boundary.localityIds, 122 | ), 123 | } 124 | 125 | if (boundary.rect) { 126 | result = { 127 | ...result, 128 | 'boundary.rect.min_lat': boundary.rect.minLat, 129 | 'boundary.rect.min_lon': boundary.rect.minLon, 130 | 'boundary.rect.max_lat': boundary.rect.maxLat, 131 | 'boundary.rect.max_lon': boundary.rect.maxLon, 132 | } 133 | } 134 | 135 | return result 136 | } 137 | 138 | function transformFocusParam( 139 | focusPoint?: GetFeaturesParams['focus'], 140 | ): FocusApi { 141 | if (!focusPoint) return {} 142 | 143 | return { 144 | 'focus.weight': focusPoint.weight, 145 | 'focus.function': focusPoint.function, 146 | 'focus.scale': focusPoint.scale, 147 | } 148 | } 149 | 150 | export function createGetFeatures(argConfig: ArgumentConfig) { 151 | const config = getServiceConfig(argConfig) 152 | 153 | return function getFeatures( 154 | text: string, 155 | coords?: Coordinates, 156 | params: GetFeaturesParams = {}, 157 | options?: RequestOptions, 158 | ): Promise { 159 | const { host, headers } = getGeocoderHost(config) 160 | const { sources, layers, limit, boundary, focus, ...rest } = params 161 | 162 | const searchParams = { 163 | text, 164 | lang: 'no', 165 | ...getPositionParamsFromGeolocationResult(coords), 166 | ...transformBoundaryParam(boundary), 167 | ...transformFocusParam(focus), 168 | sources: stringifyCommaSeparatedList(sources), 169 | layers: stringifyCommaSeparatedList(layers), 170 | size: limit, 171 | ...rest, 172 | } 173 | 174 | const url = `${host}/autocomplete` 175 | return get<{ features?: Feature[] }>( 176 | url, 177 | searchParams, 178 | headers, 179 | config.fetch, 180 | options, 181 | ).then((data) => data.features || []) 182 | } 183 | } 184 | 185 | export interface GetFeaturesReverseParam { 186 | radius?: number 187 | size?: number 188 | layers?: string[] 189 | } 190 | 191 | export function createGetFeaturesReverse(argConfig: ArgumentConfig) { 192 | const config = getServiceConfig(argConfig) 193 | 194 | return function getFeaturesReverse( 195 | coords: Coordinates, 196 | params: GetFeaturesReverseParam = {}, 197 | options?: RequestOptions, 198 | ): Promise { 199 | const { host, headers } = getGeocoderHost(config) 200 | 201 | const searchParams = { 202 | 'point.lat': coords.latitude, 203 | 'point.lon': coords.longitude, 204 | 'boundary.circle.radius': params.radius, 205 | size: params.size, 206 | layers: 207 | params.layers && Array.isArray(params.layers) 208 | ? params.layers.join(',') 209 | : undefined, 210 | } 211 | 212 | const url = `${host}/reverse` 213 | return get<{ features?: Feature[] }>( 214 | url, 215 | searchParams, 216 | headers, 217 | config.fetch, 218 | options, 219 | ).then((data) => data.features || []) 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/http.ts: -------------------------------------------------------------------------------- 1 | import qs from 'qs' 2 | import cleanDeep from 'clean-deep' 3 | import { Response, RequestInfo, RequestInit } from 'node-fetch' 4 | 5 | import fetch from './fetch' 6 | 7 | /** 8 | * Options to be passed on to the fetch request. 9 | * You can use this to add custom headers or a signal for use with an AbortController. 10 | */ 11 | export type RequestOptions = Pick 12 | 13 | const DEFAULT_HEADERS = { 14 | Accept: 'application/json', 15 | 'Content-Type': 'application/json', 16 | } 17 | 18 | function delayedPromise( 19 | callback: () => Promise, 20 | delay: number, 21 | ): Promise { 22 | return new Promise((resolve, reject) => { 23 | setTimeout(() => callback().then(resolve).catch(reject), delay) 24 | }) 25 | } 26 | 27 | function retryIfNecessary( 28 | response: Response, 29 | call: () => Promise, 30 | ): Response | PromiseLike { 31 | if (response.status !== 429) { 32 | return response 33 | } 34 | 35 | const delay = Math.floor(Math.random() * 200) 36 | return delayedPromise(call, delay) 37 | } 38 | 39 | function responseHandler(response: Response): Response | PromiseLike { 40 | if (!response.ok) { 41 | throw Error(response.statusText) 42 | } 43 | return response 44 | } 45 | 46 | export function get( 47 | url: string, 48 | params?: Record, 49 | headers?: Record, 50 | customFetch?: ( 51 | url: RequestInfo, 52 | init?: RequestInit | undefined, 53 | ) => Promise, 54 | { signal, headers: requestHeaders }: RequestOptions = {}, 55 | ): Promise { 56 | const fetcher = customFetch || fetch 57 | 58 | const call = (): Promise => 59 | fetcher(`${url}?${qs.stringify(params)}`, { 60 | method: 'get', 61 | signal, 62 | headers: { ...DEFAULT_HEADERS, ...headers, ...requestHeaders }, 63 | }) 64 | 65 | return call() 66 | .then((response) => retryIfNecessary(response, call)) 67 | .then(responseHandler) 68 | .then((res) => res.json()) 69 | .then( 70 | (data) => 71 | cleanDeep(data, { 72 | emptyArrays: false, 73 | emptyStrings: false, 74 | }) as T, 75 | ) 76 | } 77 | 78 | export function post( 79 | url: string, 80 | params?: Record, 81 | headers?: Record, 82 | customFetch?: ( 83 | url: RequestInfo, 84 | init?: RequestInit | undefined, 85 | ) => Promise, 86 | { signal, headers: requestHeaders }: RequestOptions = {}, 87 | ): Promise { 88 | const fetcher = customFetch || fetch 89 | 90 | const call = (): Promise => 91 | fetcher(url, { 92 | method: 'post', 93 | signal, 94 | headers: { ...DEFAULT_HEADERS, ...headers, ...requestHeaders }, 95 | body: JSON.stringify(params), 96 | }) 97 | 98 | return call() 99 | .then((response) => retryIfNecessary(response, call)) 100 | .then(responseHandler) 101 | .then((res) => res.json()) 102 | .then( 103 | (data) => 104 | cleanDeep(data, { 105 | emptyArrays: false, 106 | emptyStrings: false, 107 | }) as T, 108 | ) 109 | } 110 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import 'regenerator-runtime/runtime' 2 | 3 | import createEnturClient from './client' 4 | export type { EnturClient, EnturService } from './client' 5 | 6 | export { 7 | convertFeatureToLocation, 8 | convertPositionToBbox, 9 | throttler, 10 | } from './utils' 11 | 12 | export * as JourneyPlannerTypes from './journeyPlanner/types' 13 | 14 | export * as GeocoderTypes from './geocoder/types' 15 | export type { GeocoderClient } from './geocoder' 16 | 17 | export * as MobilityTypes from './mobility/types' 18 | export type { MobilityClient } from './mobility' 19 | 20 | export * as NsrTypes from './nsr/types' 21 | export type { NsrClient } from './nsr' 22 | 23 | export type { RequestOptions } from './http' 24 | 25 | export { TypeName } from './nearest/types' 26 | export type { NearestPlace } from './nearest/types' 27 | 28 | export { getTripPatternsQuery } from './trip' 29 | export type { 30 | TripPattern, 31 | GetTripPatternsParams, 32 | InputWhiteListed, 33 | InputBanned, 34 | TransportSubmodeParam, 35 | } from './trip' 36 | 37 | export * from './constants/featureCategory' 38 | 39 | export { journeyPlannerQuery, nsrQuery } from './api' 40 | 41 | export type { DeparturesById } from './departure' 42 | export { County } from './geocoderLegacy/countyIds' 43 | export type { 44 | GetFeaturesParams, 45 | GetFeaturesReverseParam, 46 | } from './geocoderLegacy' 47 | 48 | export default createEnturClient 49 | 50 | export type { Authority } from './fields/Authority' 51 | export type { BikeRentalStation } from './fields/BikeRentalStation' 52 | export type { 53 | BookingMethod, 54 | BookingContact, 55 | BookingAccess, 56 | BookingArrangement, 57 | BookWhen, 58 | } from './fields/BookingArrangement' 59 | export type { Departure } from './fields/Departure' 60 | export type { 61 | EstimatedCall, 62 | IntermediateEstimatedCall, 63 | } from './fields/EstimatedCall' 64 | export type { Interchange } from './fields/Interchange' 65 | export type { Leg } from './fields/Leg' 66 | export type { Line } from './fields/Line' 67 | export type { Notice } from './fields/Notice' 68 | export type { Operator } from './fields/Operator' 69 | export type { Place } from './fields/Place' 70 | export type { PointsOnLink } from './fields/PointsOnLink' 71 | export type { Quay } from './fields/Quay' 72 | export type { ServiceJourney } from './fields/ServiceJourney' 73 | export type { 74 | Situation, 75 | ReportType, 76 | ValidityPeriod, 77 | InfoLink, 78 | } from './fields/Situation' 79 | export type { StopPlace } from './fields/StopPlace' 80 | 81 | export type { Coordinates } from './types/Coordinates' 82 | export type { DestinationDisplay } from './types/DestinationDisplay' 83 | export type { Feature } from './types/Feature' 84 | export type { FlexibleLineType } from './types/FlexibleLineType' 85 | export * from './types/Mode' 86 | export type { Location } from './types/Location' 87 | export type { MultilingualString } from './types/MultilingualString' 88 | export type { 89 | StopPlaceDetails, 90 | LimitationStatusType, 91 | WaitingRoomEquipment, 92 | ShelterEquipment, 93 | SanitaryEquipment, 94 | TicketingEquipment, 95 | StopPlaceFacilitiesStopPlace, 96 | StopPlaceFacilitiesParking, 97 | ParkingVehicle, 98 | StopPlaceFacilities, 99 | } from './types/StopPlace' 100 | -------------------------------------------------------------------------------- /src/journeyPlanner/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | '@typescript-eslint/array-type': 'off', 4 | '@typescript-eslint/ban-types': 'off', 5 | 'tsdoc/syntax': 'off', 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /src/mobility/getOperators/index.ts: -------------------------------------------------------------------------------- 1 | import { RequestOptions } from '../../http' 2 | import { getServiceConfig, ArgumentConfig } from '../../config' 3 | import { mobilityQuery } from '../../api' 4 | 5 | import { Operator } from '../types' 6 | 7 | import getOperatorsQuery from './query' 8 | 9 | export default function createGetOperators(argConfig: ArgumentConfig) { 10 | const config = getServiceConfig(argConfig) 11 | 12 | return async function getOperators( 13 | options?: RequestOptions, 14 | ): Promise { 15 | const data = await mobilityQuery<{ operators: Operator[] }>( 16 | getOperatorsQuery, 17 | {}, 18 | config, 19 | options, 20 | ) 21 | 22 | return data?.operators || [] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/mobility/getOperators/query.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 | query { 3 | operators { 4 | id 5 | name { 6 | translation { 7 | language 8 | value 9 | } 10 | } 11 | } 12 | } 13 | ` 14 | -------------------------------------------------------------------------------- /src/mobility/getStations/index.ts: -------------------------------------------------------------------------------- 1 | import { RequestOptions } from '../../http' 2 | import { getServiceConfig, ArgumentConfig } from '../../config' 3 | import { mobilityQuery } from '../../api' 4 | 5 | import { Station } from '../types' 6 | 7 | import getStationsQuery from './query' 8 | 9 | export interface GetStationsParams { 10 | /** The latitude coordinate. */ 11 | lat: number 12 | /** The longitude coordinate. */ 13 | lon: number 14 | /** The radius in meters from the coordinate pair in which to find stations. */ 15 | range: number 16 | /** The maximum number of stations to return. */ 17 | count?: number 18 | /** The maximum number of stations to return. */ 19 | codespaces?: string[] 20 | /** Return only stations of the given systems. */ 21 | systems?: string[] 22 | /** Return only stations of the given operators. */ 23 | operators?: string[] 24 | } 25 | 26 | export default function createGetStations(argConfig: ArgumentConfig) { 27 | const config = getServiceConfig(argConfig) 28 | 29 | return async function getStations( 30 | params: GetStationsParams, 31 | options?: RequestOptions, 32 | ): Promise { 33 | const data = await mobilityQuery<{ stations: Station[] }>( 34 | getStationsQuery, 35 | params, 36 | config, 37 | options, 38 | ) 39 | 40 | return data?.stations || [] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/mobility/getStations/query.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 | query ($lat: Float!, $lon: Float!, $range: Int!, $count: Int, $codespaces: [String], $systems: [String], $operators: [String]) { 3 | stations( 4 | lon: $lon, 5 | lat: $lat, 6 | range: $range, 7 | count: $count, 8 | codespaces: $codespaces, 9 | systems: $systems, 10 | operators: $operators 11 | ) { 12 | id 13 | name { 14 | translation { 15 | language 16 | value 17 | } 18 | } 19 | lat 20 | lon 21 | address 22 | capacity 23 | rentalUris { 24 | android 25 | ios 26 | web 27 | } 28 | numBikesAvailable 29 | numDocksAvailable 30 | isInstalled 31 | isRenting 32 | isReturning 33 | lastReported 34 | system { 35 | id 36 | language 37 | name { 38 | translation { 39 | language 40 | value 41 | } 42 | } 43 | shortName { 44 | translation { 45 | language 46 | value 47 | } 48 | } 49 | operator { 50 | id 51 | name { 52 | translation { 53 | language 54 | value 55 | } 56 | } 57 | } 58 | url 59 | purchaseUrl 60 | startDate 61 | phoneNumber 62 | email 63 | feedContactEmail 64 | timezone 65 | licenseUrl 66 | rentalApps { 67 | ios { 68 | storeUri 69 | discoveryUri 70 | } 71 | android { 72 | storeUri 73 | discoveryUri 74 | } 75 | } 76 | } 77 | pricingPlans { 78 | id 79 | url 80 | name { 81 | translation { 82 | language 83 | value 84 | } 85 | } 86 | currency 87 | price 88 | isTaxable 89 | description { 90 | translation { 91 | language 92 | value 93 | } 94 | } 95 | perKmPricing { 96 | start 97 | rate 98 | interval 99 | end 100 | } 101 | perMinPricing { 102 | start 103 | rate 104 | interval 105 | end 106 | } 107 | surgePricing 108 | } 109 | vehicleTypesAvailable { 110 | vehicleType { 111 | formFactor 112 | propulsionType 113 | id 114 | maxRangeMeters 115 | name { 116 | translation { 117 | language 118 | value 119 | } 120 | } 121 | } 122 | count 123 | } 124 | } 125 | } 126 | ` 127 | -------------------------------------------------------------------------------- /src/mobility/getStationsById/index.ts: -------------------------------------------------------------------------------- 1 | import { RequestOptions } from '../../http' 2 | import { getServiceConfig, ArgumentConfig } from '../../config' 3 | import { mobilityQuery } from '../../api' 4 | 5 | import { Station } from '../types' 6 | 7 | import getStationsQuery from './query' 8 | 9 | export interface GetStationsByIdParams { 10 | /** Return only stations with the given IDs. */ 11 | stationIds: string[] 12 | } 13 | 14 | export default function createGetStationsById(argConfig: ArgumentConfig) { 15 | const config = getServiceConfig(argConfig) 16 | 17 | return async function getStationsById( 18 | params: GetStationsByIdParams, 19 | options?: RequestOptions, 20 | ): Promise { 21 | const data = await mobilityQuery<{ stationsById: Station[] }>( 22 | getStationsQuery, 23 | params, 24 | config, 25 | options, 26 | ) 27 | 28 | return data?.stationsById || [] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/mobility/getStationsById/query.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 | query ($stationIds: [String]!) { 3 | stationsById( 4 | ids: $stationIds 5 | ) { 6 | id 7 | name { 8 | translation { 9 | language 10 | value 11 | } 12 | } 13 | lat 14 | lon 15 | address 16 | capacity 17 | rentalUris { 18 | android 19 | ios 20 | web 21 | } 22 | numBikesAvailable 23 | numDocksAvailable 24 | isInstalled 25 | isRenting 26 | isReturning 27 | lastReported 28 | system { 29 | id 30 | language 31 | name { 32 | translation { 33 | language 34 | value 35 | } 36 | } 37 | shortName { 38 | translation { 39 | language 40 | value 41 | } 42 | } 43 | operator { 44 | id 45 | name { 46 | translation { 47 | language 48 | value 49 | } 50 | } 51 | } 52 | url 53 | purchaseUrl 54 | startDate 55 | phoneNumber 56 | email 57 | feedContactEmail 58 | timezone 59 | licenseUrl 60 | rentalApps { 61 | ios { 62 | storeUri 63 | discoveryUri 64 | } 65 | android { 66 | storeUri 67 | discoveryUri 68 | } 69 | } 70 | } 71 | pricingPlans { 72 | id 73 | url 74 | name { 75 | translation { 76 | language 77 | value 78 | } 79 | } 80 | currency 81 | price 82 | isTaxable 83 | description { 84 | translation { 85 | language 86 | value 87 | } 88 | } 89 | perKmPricing { 90 | start 91 | rate 92 | interval 93 | end 94 | } 95 | perMinPricing { 96 | start 97 | rate 98 | interval 99 | end 100 | } 101 | surgePricing 102 | } 103 | vehicleTypesAvailable { 104 | vehicleType { 105 | formFactor 106 | propulsionType 107 | id 108 | maxRangeMeters 109 | name { 110 | translation { 111 | language 112 | value 113 | } 114 | } 115 | } 116 | count 117 | } 118 | } 119 | } 120 | ` 121 | -------------------------------------------------------------------------------- /src/mobility/getVehicles/index.ts: -------------------------------------------------------------------------------- 1 | import { RequestOptions } from '../../http' 2 | import { getServiceConfig, ArgumentConfig } from '../../config' 3 | import { mobilityQuery } from '../../api' 4 | 5 | import { FormFactor, PropulsionType, Vehicle } from '../types' 6 | 7 | import getVehiclesQuery from './query' 8 | 9 | export interface GetVehiclesParams { 10 | /** The latitude coordinate. */ 11 | lat: number 12 | /** The longitude coordinate. */ 13 | lon: number 14 | /** The radius in meters from the coordinate pair in which to find vehicles. */ 15 | range: number 16 | /** The maximum number of vehicles to return. */ 17 | count?: number 18 | /** Return only vehicles of the given operators. */ 19 | operators?: string[] 20 | /** The maximum number of vehicles to return. */ 21 | codespaces?: string[] 22 | /** Return only vehicles of the given form factors. */ 23 | formFactors?: FormFactor[] 24 | /** Return only vehicles of the given propulsion types. */ 25 | propulsionTypes?: PropulsionType[] 26 | /** Whether you want to return vehicles that are already reserved. The default is false. */ 27 | includeReserved?: boolean 28 | /** Whether you want to return vehicles that are disabled. The default is false. */ 29 | includeDisabled?: boolean 30 | } 31 | 32 | export default function createGetVehicles(argConfig: ArgumentConfig) { 33 | const config = getServiceConfig(argConfig) 34 | 35 | return async function getVehicles( 36 | params: GetVehiclesParams, 37 | options?: RequestOptions, 38 | ): Promise { 39 | const data = await mobilityQuery<{ vehicles: Vehicle[] }>( 40 | getVehiclesQuery, 41 | params, 42 | config, 43 | options, 44 | ) 45 | 46 | return data?.vehicles || [] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/mobility/getVehicles/query.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 | query ($lat: Float!, $lon: Float!, $range: Int!, $count: Int, $operators: [String], $codespaces: [String], $formFactors: [FormFactor], $propulsionTypes: [PropulsionType], $includeReserved: Boolean = false, $includeDisabled: Boolean = false) { 3 | vehicles( 4 | range: $range, 5 | propulsionTypes: $propulsionTypes, 6 | operators: $operators, 7 | codespaces: $codespaces, 8 | includeReserved: $includeReserved, 9 | lon: $lon, 10 | lat: $lat, 11 | count: $count, 12 | formFactors: $formFactors, 13 | includeDisabled: $includeDisabled 14 | ) { 15 | lat 16 | lon 17 | vehicleType { 18 | id 19 | formFactor 20 | propulsionType 21 | } 22 | pricingPlan { 23 | description { 24 | translation { 25 | language 26 | value 27 | } 28 | } 29 | id 30 | currency 31 | isTaxable 32 | name { 33 | translation { 34 | language 35 | value 36 | } 37 | } 38 | perKmPricing { 39 | start 40 | rate 41 | interval 42 | end 43 | } 44 | perMinPricing { 45 | start 46 | rate 47 | interval 48 | end 49 | } 50 | price 51 | surgePricing 52 | url 53 | } 54 | system { 55 | name { 56 | translation { 57 | language 58 | value 59 | } 60 | } 61 | email 62 | feedContactEmail 63 | id 64 | licenseUrl 65 | language 66 | phoneNumber 67 | operator { 68 | id 69 | name { 70 | translation { 71 | language 72 | value 73 | } 74 | } 75 | } 76 | purchaseUrl 77 | startDate 78 | shortName { 79 | translation { 80 | language 81 | value 82 | } 83 | } 84 | timezone 85 | url 86 | rentalApps { 87 | ios { 88 | storeUri 89 | discoveryUri 90 | } 91 | android { 92 | storeUri 93 | discoveryUri 94 | } 95 | } 96 | } 97 | isDisabled 98 | isReserved 99 | id 100 | currentRangeMeters 101 | rentalUris { 102 | android 103 | ios 104 | web 105 | } 106 | } 107 | } 108 | ` 109 | -------------------------------------------------------------------------------- /src/mobility/index.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentConfig } from '../config' 2 | 3 | import { default as createGetStations } from './getStations' 4 | import { default as createGetStationsById } from './getStationsById' 5 | import { default as createGetVehicles } from './getVehicles' 6 | import { default as createGetOperators } from './getOperators' 7 | 8 | export interface MobilityClient { 9 | getOperators: ReturnType 10 | getStations: ReturnType 11 | getStationsById: ReturnType 12 | getVehicles: ReturnType 13 | } 14 | 15 | export default function createClient(config: ArgumentConfig): MobilityClient { 16 | return { 17 | getOperators: createGetOperators(config), 18 | getStations: createGetStations(config), 19 | getStationsById: createGetStationsById(config), 20 | getVehicles: createGetVehicles(config), 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/mobility/types.ts: -------------------------------------------------------------------------------- 1 | export type { GetVehiclesParams } from './getVehicles' 2 | 3 | export interface Translation { 4 | language: string 5 | value: string 6 | } 7 | 8 | export interface TranslatedString { 9 | translation: Translation[] 10 | } 11 | 12 | export enum FormFactor { 13 | BICYCLE = 'BICYCLE', 14 | CAR = 'CAR', 15 | MOPED = 'MOPED', 16 | SCOOTER = 'SCOOTER', 17 | OTHER = 'OTHER', 18 | } 19 | 20 | export enum PropulsionType { 21 | HUMAN = 'HUMAN', 22 | ELECTRIC_ASSIST = 'ELECTRIC_ASSIST', 23 | ELECTRIC = 'ELECTRIC', 24 | COMBUSTION = 'COMBUSTION', 25 | } 26 | 27 | export interface VehicleType { 28 | id: string 29 | formFactor: FormFactor 30 | propulsionType: PropulsionType 31 | maxRangeMeters?: number 32 | name?: string 33 | } 34 | 35 | export interface PricingSegment { 36 | start: number 37 | rate: number 38 | interval: number 39 | end: number 40 | } 41 | 42 | export interface PricingPlan { 43 | id: string 44 | url?: string 45 | name: TranslatedString 46 | currency: string 47 | price: number 48 | isTaxable: boolean 49 | description: TranslatedString 50 | perKmPricing?: PricingSegment[] 51 | perMinPricing?: PricingSegment[] 52 | surgePricing?: boolean 53 | } 54 | 55 | export interface RentalUris { 56 | android?: string 57 | ios?: string 58 | web?: string 59 | } 60 | 61 | export interface RentalApp { 62 | storeUri: string 63 | discoveryUri: string 64 | } 65 | 66 | export interface RentalApps { 67 | ios?: RentalApp 68 | android?: RentalApp 69 | } 70 | 71 | export interface Operator { 72 | id: string 73 | name: TranslatedString 74 | } 75 | 76 | export interface System { 77 | id: string 78 | language: string 79 | name: TranslatedString 80 | shortName?: TranslatedString 81 | operator: Operator 82 | url?: string 83 | purchaseUrl?: string 84 | startDate?: string 85 | phoneNumber?: string 86 | email?: string 87 | feedContactEmail?: string 88 | timezone: string 89 | licenseUrl?: string 90 | rentalApps?: RentalApps 91 | } 92 | 93 | export interface Vehicle { 94 | id: string 95 | lat: number 96 | lon: number 97 | isReserved: boolean 98 | isDisabled: boolean 99 | currentRangeMeters: number 100 | vehicleType: VehicleType 101 | pricingPlan?: PricingPlan 102 | rentalUris?: RentalUris 103 | system: System 104 | } 105 | 106 | export interface Station { 107 | id: string 108 | name: TranslatedString 109 | lat: number 110 | lon: number 111 | address?: string 112 | capacity?: number 113 | rentalUris?: RentalUris 114 | numBikesAvailable: number 115 | numDocksAvailable?: number 116 | isInstalled: boolean 117 | isRenting: boolean 118 | isReturning: boolean 119 | lastReported: number 120 | system: System 121 | vehicleTypesAvailable?: Array<{ 122 | vehicleType: VehicleType 123 | count: number 124 | }> 125 | pricingPlans: PricingPlan[] 126 | } 127 | -------------------------------------------------------------------------------- /src/nearest/index.ts: -------------------------------------------------------------------------------- 1 | import { RequestOptions } from '../http' 2 | import { journeyPlannerQuery } from '../api' 3 | 4 | import { getNearestPlacesQuery } from './query' 5 | 6 | import { TransportMode } from '../types/Mode' 7 | import { Coordinates } from '../types/Coordinates' 8 | import { NearestPlace, TypeName } from './types' 9 | 10 | import { getServiceConfig, ArgumentConfig } from '../config' 11 | import { isTruthy } from '../utils' 12 | 13 | type FilterPlaceType = 14 | | 'bicycleRent' 15 | | 'bikePark' 16 | | 'carPark' 17 | | 'quay' 18 | | 'stopPlace' 19 | 20 | const ALL_PLACE_TYPES = [ 21 | 'bicycleRent', 22 | 'bikePark', 23 | 'carPark', 24 | 'quay', 25 | 'stopPlace', 26 | ] 27 | 28 | function convertTypeNameToFilterPlaceType( 29 | typeName: TypeName, 30 | ): FilterPlaceType | undefined { 31 | switch (typeName) { 32 | case TypeName.BIKE_PARK: 33 | return 'bikePark' 34 | case TypeName.BIKE_RENTAL_STATION: 35 | return 'bicycleRent' 36 | case TypeName.CAR_PARK: 37 | return 'carPark' 38 | case TypeName.QUAY: 39 | return 'quay' 40 | case TypeName.STOP_PLACE: 41 | return 'stopPlace' 42 | default: 43 | return undefined 44 | } 45 | } 46 | 47 | type NearestParams = { 48 | maximumDistance?: number 49 | maximumResults?: number 50 | filterByPlaceTypes?: TypeName[] 51 | filterByModes?: TransportMode[] 52 | filterByInUse?: boolean 53 | multiModalMode?: 'parent' | 'child' | 'all' 54 | } 55 | 56 | type NearestData = { 57 | nearest?: { 58 | edges?: Array<{ 59 | node: { 60 | distance: number 61 | place: { 62 | id: string 63 | __typename: TypeName 64 | latitude: number 65 | longitude: number 66 | } 67 | } 68 | }> 69 | } 70 | } 71 | 72 | export function createGetNearestPlaces(argConfig: ArgumentConfig) { 73 | const config = getServiceConfig(argConfig) 74 | 75 | return function getNearestPlaces( 76 | coordinates: Coordinates, 77 | params: NearestParams = {}, 78 | options?: RequestOptions, 79 | ): Promise { 80 | const { latitude, longitude } = coordinates 81 | 82 | const { 83 | maximumDistance = 2000, 84 | maximumResults = 20, 85 | filterByInUse = false, 86 | filterByModes, 87 | filterByPlaceTypes = ALL_PLACE_TYPES, 88 | multiModalMode = 'parent', 89 | } = params 90 | 91 | const variables = { 92 | latitude, 93 | longitude, 94 | maximumDistance, 95 | maximumResults, 96 | filterByInUse, 97 | filterByModes, 98 | filterByPlaceTypes, 99 | multiModalMode, 100 | } 101 | 102 | if (params.filterByPlaceTypes) { 103 | variables.filterByPlaceTypes = params.filterByPlaceTypes 104 | .map(convertTypeNameToFilterPlaceType) 105 | .filter(isTruthy) 106 | } 107 | 108 | return journeyPlannerQuery( 109 | getNearestPlacesQuery, 110 | variables, 111 | config, 112 | options, 113 | ).then((data) => 114 | (data?.nearest?.edges || []).map(({ node }) => { 115 | const { distance, place } = node 116 | 117 | return { 118 | distance, 119 | id: place.id, 120 | type: place.__typename, // eslint-disable-line no-underscore-dangle 121 | latitude: place.latitude, 122 | longitude: place.longitude, 123 | } 124 | }), 125 | ) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/nearest/query.ts: -------------------------------------------------------------------------------- 1 | export const getNearestPlacesQuery = ` 2 | query ( 3 | $latitude: Float!, 4 | $longitude: Float!, 5 | $maximumDistance: Int, 6 | $maximumResults: Int, 7 | $filterByPlaceTypes: [FilterPlaceType], 8 | $filterByModes: [Mode], 9 | $filterByInUse: Boolean, 10 | $multiModalMode: MultiModalMode 11 | ) { 12 | nearest( 13 | latitude: $latitude, 14 | longitude: $longitude, 15 | maximumDistance: $maximumDistance, 16 | maximumResults: $maximumResults, 17 | filterByPlaceTypes: $filterByPlaceTypes, 18 | filterByModes: $filterByModes, 19 | filterByInUse: $filterByInUse, 20 | multiModalMode: $multiModalMode 21 | ) { 22 | edges { 23 | node { 24 | distance 25 | place { 26 | __typename 27 | id 28 | latitude 29 | longitude 30 | } 31 | } 32 | } 33 | } 34 | } 35 | ` 36 | -------------------------------------------------------------------------------- /src/nearest/types.ts: -------------------------------------------------------------------------------- 1 | export enum TypeName { 2 | BIKE_PARK = 'BikePark', 3 | BIKE_RENTAL_STATION = 'BikeRentalStation', 4 | CAR_PARK = 'CarPark', 5 | QUAY = 'Quay', 6 | STOP_PLACE = 'StopPlace', 7 | } 8 | 9 | export interface NearestPlace { 10 | id: string 11 | type: TypeName 12 | distance: number 13 | latitude: number 14 | longitude: number 15 | } 16 | -------------------------------------------------------------------------------- /src/nsr/getFareZone/index.ts: -------------------------------------------------------------------------------- 1 | import { get, RequestOptions } from '../../http' 2 | import { getServiceConfig, getNSRHost, ArgumentConfig } from '../../config' 3 | 4 | import { FareZone } from '../types' 5 | 6 | export default function createGetFareZone(argConfig: ArgumentConfig) { 7 | const config = getServiceConfig(argConfig) 8 | const { host, headers } = getNSRHost(config) 9 | 10 | return async function getFareZone( 11 | id: string, 12 | options?: RequestOptions, 13 | ): Promise { 14 | const url = `${host}/read/fare-zones/${id}` 15 | const data = await get( 16 | url, 17 | undefined, 18 | headers, 19 | config.fetch, 20 | options, 21 | ) 22 | 23 | return data 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/nsr/getGroupOfStopPlaces/index.ts: -------------------------------------------------------------------------------- 1 | import { get, RequestOptions } from '../../http' 2 | import { getServiceConfig, getNSRHost, ArgumentConfig } from '../../config' 3 | 4 | import { GroupOfStopPlaces } from '../types' 5 | 6 | export default function createGetGroupOfStopPlaces(argConfig: ArgumentConfig) { 7 | const config = getServiceConfig(argConfig) 8 | const { host, headers } = getNSRHost(config) 9 | 10 | return async function getGroupOfStopPlaces( 11 | id: string, 12 | options?: RequestOptions, 13 | ): Promise { 14 | const url = `${host}/read/groups-of-stop-places/${id}` 15 | const data = await get( 16 | url, 17 | undefined, 18 | headers, 19 | config.fetch, 20 | options, 21 | ) 22 | 23 | return data 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/nsr/getParking/index.ts: -------------------------------------------------------------------------------- 1 | import { get, RequestOptions } from '../../http' 2 | import { getServiceConfig, getNSRHost, ArgumentConfig } from '../../config' 3 | 4 | import { Parking } from '../types' 5 | 6 | export default function createGetParking(argConfig: ArgumentConfig) { 7 | const config = getServiceConfig(argConfig) 8 | const { host, headers } = getNSRHost(config) 9 | 10 | return async function getParking( 11 | id: string, 12 | options?: RequestOptions, 13 | ): Promise { 14 | const url = `${host}/read/parkings/${id}` 15 | const data = await get( 16 | url, 17 | undefined, 18 | headers, 19 | config.fetch, 20 | options, 21 | ) 22 | 23 | return data 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/nsr/getParkingsForStopPlace/index.ts: -------------------------------------------------------------------------------- 1 | import { get, RequestOptions } from '../../http' 2 | import { getServiceConfig, getNSRHost, ArgumentConfig } from '../../config' 3 | 4 | import { Parking } from '../types' 5 | 6 | export default function createGetParkingsForStopPlace( 7 | argConfig: ArgumentConfig, 8 | ) { 9 | const config = getServiceConfig(argConfig) 10 | const { host, headers } = getNSRHost(config) 11 | 12 | return async function getParkingsForStopPlace( 13 | id: string, 14 | options?: RequestOptions, 15 | ): Promise { 16 | const url = `${host}/read/stop-places/${id}/parkings` 17 | const data = await get( 18 | url, 19 | undefined, 20 | headers, 21 | config.fetch, 22 | options, 23 | ) 24 | 25 | return data || [] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/nsr/getQuay/index.ts: -------------------------------------------------------------------------------- 1 | import { get, RequestOptions } from '../../http' 2 | import { getServiceConfig, getNSRHost, ArgumentConfig } from '../../config' 3 | 4 | import { Quay } from '../types' 5 | 6 | export default function createGetQuay(argConfig: ArgumentConfig) { 7 | const config = getServiceConfig(argConfig) 8 | const { host, headers } = getNSRHost(config) 9 | 10 | return async function getQuay( 11 | id: string, 12 | options?: RequestOptions, 13 | ): Promise { 14 | const url = `${host}/read/quays/${id}` 15 | const data = await get( 16 | url, 17 | undefined, 18 | headers, 19 | config.fetch, 20 | options, 21 | ) 22 | 23 | return data 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/nsr/getStopPlace/index.ts: -------------------------------------------------------------------------------- 1 | import { get, RequestOptions } from '../../http' 2 | import { getServiceConfig, getNSRHost, ArgumentConfig } from '../../config' 3 | 4 | import { StopPlace } from '../types' 5 | 6 | export default function createGetStopPlace(argConfig: ArgumentConfig) { 7 | const config = getServiceConfig(argConfig) 8 | const { host, headers } = getNSRHost(config) 9 | 10 | return async function getStopPlace( 11 | id: string, 12 | options?: RequestOptions, 13 | ): Promise { 14 | const url = `${host}/read/stop-places/${id}` 15 | const data = await get( 16 | url, 17 | undefined, 18 | headers, 19 | config.fetch, 20 | options, 21 | ) 22 | 23 | return data 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/nsr/getStopPlaceForQuay/index.ts: -------------------------------------------------------------------------------- 1 | import { get, RequestOptions } from '../../http' 2 | import { getServiceConfig, getNSRHost, ArgumentConfig } from '../../config' 3 | 4 | import { StopPlace } from '../types' 5 | 6 | export default function createGetStopPlaceForQuay(argConfig: ArgumentConfig) { 7 | const config = getServiceConfig(argConfig) 8 | const { host, headers } = getNSRHost(config) 9 | 10 | return async function getStopPlaceForQuay( 11 | id: string, 12 | options?: RequestOptions, 13 | ): Promise { 14 | const url = `${host}/read/quays/${id}/stop-place` 15 | const data = await get( 16 | url, 17 | undefined, 18 | headers, 19 | config.fetch, 20 | options, 21 | ) 22 | 23 | return data 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/nsr/getTariffZone/index.ts: -------------------------------------------------------------------------------- 1 | import { get, RequestOptions } from '../../http' 2 | import { getServiceConfig, getNSRHost, ArgumentConfig } from '../../config' 3 | 4 | import { TariffZone } from '../types' 5 | 6 | export default function createGetTariffZone(argConfig: ArgumentConfig) { 7 | const config = getServiceConfig(argConfig) 8 | const { host, headers } = getNSRHost(config) 9 | 10 | return async function getTariffZone( 11 | id: string, 12 | options?: RequestOptions, 13 | ): Promise { 14 | const url = `${host}/read/tariff-zones/${id}` 15 | const data = await get( 16 | url, 17 | undefined, 18 | headers, 19 | config.fetch, 20 | options, 21 | ) 22 | 23 | return data 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/nsr/getTopographicPlace/index.ts: -------------------------------------------------------------------------------- 1 | import { get, RequestOptions } from '../../http' 2 | import { getServiceConfig, getNSRHost, ArgumentConfig } from '../../config' 3 | 4 | import { TopographicPlace } from '../types' 5 | 6 | export default function createGetTopographicPlace(argConfig: ArgumentConfig) { 7 | const config = getServiceConfig(argConfig) 8 | const { host, headers } = getNSRHost(config) 9 | 10 | return async function getTopographicPlace( 11 | id: string, 12 | options?: RequestOptions, 13 | ): Promise { 14 | const url = `${host}/read/topographic-places/${id}` 15 | const data = await get( 16 | url, 17 | undefined, 18 | headers, 19 | config.fetch, 20 | options, 21 | ) 22 | 23 | return data 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/nsr/index.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentConfig } from '../config' 2 | 3 | import { default as createGetFareZone } from './getFareZone' 4 | import { default as createGetGroupOfStopPlaces } from './getGroupOfStopPlaces' 5 | import { default as createGetParking } from './getParking' 6 | import { default as createGetParkingsForStopPlace } from './getParkingsForStopPlace' 7 | import { default as createGetQuay } from './getQuay' 8 | import { default as createGetStopPlace } from './getStopPlace' 9 | import { default as createGetStopPlaceForQuay } from './getStopPlaceForQuay' 10 | import { default as createGetTariffZone } from './getTariffZone' 11 | import { default as createGetTopographicPlace } from './getTopographicPlace' 12 | 13 | export interface NsrClient { 14 | getFareZone: ReturnType 15 | getGroupOfStopPlaces: ReturnType 16 | getParking: ReturnType 17 | getParkingsForStopPlace: ReturnType 18 | getQuay: ReturnType 19 | getStopPlace: ReturnType 20 | getStopPlaceForQuay: ReturnType 21 | getTariffZone: ReturnType 22 | getTopographicPlace: ReturnType 23 | } 24 | 25 | export default function createClient(config: ArgumentConfig): NsrClient { 26 | return { 27 | getFareZone: createGetFareZone(config), 28 | getGroupOfStopPlaces: createGetGroupOfStopPlaces(config), 29 | getParking: createGetParking(config), 30 | getParkingsForStopPlace: createGetParkingsForStopPlace(config), 31 | getQuay: createGetQuay(config), 32 | getStopPlace: createGetStopPlace(config), 33 | getStopPlaceForQuay: createGetStopPlaceForQuay(config), 34 | getTariffZone: createGetTariffZone(config), 35 | getTopographicPlace: createGetTopographicPlace(config), 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/stopPlace/index.ts: -------------------------------------------------------------------------------- 1 | import { RequestOptions } from '../http' 2 | import { journeyPlannerQuery } from '../api' 3 | 4 | import { 5 | getStopPlaceQuery, 6 | getStopPlacesQuery, 7 | getParentStopPlaceQuery, 8 | getStopPlacesByBboxQuery, 9 | getQuaysForStopPlaceQuery, 10 | } from './query' 11 | 12 | import { convertPositionToBbox, forceOrder } from '../utils' 13 | import { Quay } from '../fields/Quay' 14 | 15 | import { Coordinates } from '../types/Coordinates' 16 | import { StopPlaceDetails } from '../types/StopPlace' 17 | 18 | import { getServiceConfig, ArgumentConfig } from '../config' 19 | 20 | type StopPlaceParams = { includeUnusedQuays?: boolean } 21 | 22 | export function createGetStopPlace(argConfig: ArgumentConfig) { 23 | const config = getServiceConfig(argConfig) 24 | 25 | return function getStopPlace( 26 | stopPlaceId: string, 27 | params: StopPlaceParams = {}, 28 | options?: RequestOptions, 29 | ): Promise { 30 | const { includeUnusedQuays = true, ...rest } = params 31 | const variables = { 32 | id: stopPlaceId, 33 | filterByInUse: !includeUnusedQuays, 34 | ...rest, 35 | } 36 | 37 | return journeyPlannerQuery<{ stopPlace?: StopPlaceDetails }>( 38 | getStopPlaceQuery, 39 | variables, 40 | config, 41 | options, 42 | ).then((data) => data?.stopPlace) 43 | } 44 | } 45 | 46 | export function createGetStopPlaces(argConfig: ArgumentConfig) { 47 | const config = getServiceConfig(argConfig) 48 | 49 | return function getStopPlaces( 50 | stopPlaceIds: string[], 51 | params: StopPlaceParams = {}, 52 | options?: RequestOptions, 53 | ): Promise> { 54 | if (!Array.isArray(stopPlaceIds)) { 55 | throw new Error( 56 | `getStopPlaces takes an array of strings, but got ${typeof stopPlaceIds}`, 57 | ) 58 | } 59 | 60 | if (stopPlaceIds.length === 0) { 61 | return Promise.resolve([]) 62 | } 63 | 64 | const { includeUnusedQuays = true, ...rest } = params 65 | const variables = { 66 | ids: stopPlaceIds, 67 | filterByInUse: !includeUnusedQuays, 68 | ...rest, 69 | } 70 | 71 | return journeyPlannerQuery<{ stopPlaces?: StopPlaceDetails[] }>( 72 | getStopPlacesQuery, 73 | variables, 74 | config, 75 | options, 76 | ) 77 | .then((data) => data?.stopPlaces || []) 78 | .then((stopPlaceDetails: StopPlaceDetails[]) => { 79 | return forceOrder( 80 | stopPlaceDetails, 81 | stopPlaceIds, 82 | ({ id }) => id, 83 | ) 84 | }) 85 | } 86 | } 87 | 88 | export function createGetParentStopPlace(argConfig: ArgumentConfig) { 89 | const config = getServiceConfig(argConfig) 90 | 91 | return function getParentStopPlace( 92 | stopPlaceId: string, 93 | params: StopPlaceParams = {}, 94 | options?: RequestOptions, 95 | ): Promise { 96 | const { includeUnusedQuays = true, ...rest } = params 97 | const variables = { 98 | id: stopPlaceId, 99 | filterByInUse: !includeUnusedQuays, 100 | ...rest, 101 | } 102 | 103 | return journeyPlannerQuery<{ 104 | stopPlace?: { parent?: StopPlaceDetails } 105 | }>(getParentStopPlaceQuery, variables, config, options).then( 106 | (data) => data?.stopPlace?.parent, 107 | ) 108 | } 109 | } 110 | 111 | export function createGetStopPlacesByPosition(argConfig: ArgumentConfig) { 112 | const config = getServiceConfig(argConfig) 113 | 114 | return function getStopPlacesByPosition( 115 | coordinates: Coordinates, 116 | distance = 500, 117 | params: StopPlaceParams = {}, 118 | options?: RequestOptions, 119 | ): Promise { 120 | const { includeUnusedQuays = true, ...rest } = params 121 | const variables = { 122 | ...convertPositionToBbox(coordinates, distance), 123 | filterByInUse: !includeUnusedQuays, 124 | ...rest, 125 | } 126 | 127 | return journeyPlannerQuery<{ stopPlacesByBbox?: StopPlaceDetails[] }>( 128 | getStopPlacesByBboxQuery, 129 | variables, 130 | config, 131 | options, 132 | ).then((data) => data?.stopPlacesByBbox || []) 133 | } 134 | } 135 | 136 | export function createGetQuaysForStopPlace(argConfig: ArgumentConfig) { 137 | const config = getServiceConfig(argConfig) 138 | 139 | return function getQuaysForStopPlace( 140 | stopPlaceId: string, 141 | params: StopPlaceParams = {}, 142 | options?: RequestOptions, 143 | ): Promise { 144 | const { includeUnusedQuays = true, ...rest } = params 145 | const variables = { 146 | id: stopPlaceId, 147 | filterByInUse: !includeUnusedQuays, 148 | ...rest, 149 | } 150 | 151 | return journeyPlannerQuery<{ stopPlace?: { quays?: Quay[] } }>( 152 | getQuaysForStopPlaceQuery, 153 | variables, 154 | config, 155 | options, 156 | ).then((data) => data?.stopPlace?.quays || []) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/stopPlace/query.ts: -------------------------------------------------------------------------------- 1 | import { 2 | fragmentName as quayFields, 3 | fragments as quayFragments, 4 | } from '../fields/Quay' 5 | 6 | export const getStopPlaceQuery = ` 7 | query($id: String!, $filterByInUse: Boolean) { 8 | stopPlace(id: $id) { 9 | id 10 | name 11 | description 12 | latitude 13 | longitude 14 | wheelchairBoarding 15 | weighting 16 | transportMode 17 | transportSubmode 18 | quays(filterByInUse: $filterByInUse) { 19 | ...${quayFields} 20 | } 21 | } 22 | } 23 | 24 | ${quayFragments.join('')} 25 | ` 26 | 27 | export const getStopPlacesQuery = ` 28 | query($ids: [String]!, $filterByInUse: Boolean) { 29 | stopPlaces(ids: $ids) { 30 | id 31 | name 32 | description 33 | latitude 34 | longitude 35 | wheelchairBoarding 36 | weighting 37 | transportMode 38 | transportSubmode 39 | quays(filterByInUse: $filterByInUse) { 40 | ...${quayFields} 41 | } 42 | } 43 | } 44 | 45 | ${quayFragments.join('')} 46 | ` 47 | 48 | export const getParentStopPlaceQuery = ` 49 | query($id: String!, $filterByInUse: Boolean) { 50 | stopPlace(id: $id) { 51 | parent { 52 | id 53 | name 54 | description 55 | latitude 56 | longitude 57 | wheelchairBoarding 58 | weighting 59 | transportMode 60 | transportSubmode 61 | quays(filterByInUse: $filterByInUse) { 62 | ...${quayFields} 63 | } 64 | } 65 | } 66 | } 67 | 68 | ${quayFragments.join('')} 69 | ` 70 | 71 | export const getStopPlacesByBboxQuery = ` 72 | query( 73 | $minLat: Float, 74 | $minLng: Float, 75 | $maxLng: Float, 76 | $maxLat: Float, 77 | $filterByInUse: Boolean 78 | ) { 79 | stopPlacesByBbox( 80 | minimumLatitude: $minLat, 81 | minimumLongitude: $minLng, 82 | maximumLatitude: $maxLat, 83 | maximumLongitude: $maxLng 84 | ) { 85 | id 86 | name 87 | description 88 | latitude 89 | longitude 90 | wheelchairBoarding 91 | weighting 92 | transportMode 93 | transportSubmode 94 | quays(filterByInUse: $filterByInUse) { 95 | ...${quayFields} 96 | } 97 | } 98 | } 99 | 100 | ${quayFragments.join('')} 101 | ` 102 | 103 | export const getQuaysForStopPlaceQuery = ` 104 | query($id: String!, $filterByInUse: Boolean) { 105 | stopPlace(id: $id) { 106 | quays(filterByInUse: $filterByInUse) { 107 | ...${quayFields} 108 | } 109 | } 110 | } 111 | 112 | ${quayFragments.join('')} 113 | ` 114 | -------------------------------------------------------------------------------- /src/trip/index.ts: -------------------------------------------------------------------------------- 1 | import { RequestOptions } from '../http' 2 | import { journeyPlannerQuery, getGraphqlParams } from '../api' 3 | 4 | import { getTripPatternQuery } from './query' 5 | 6 | import { legMapper } from './mapper' 7 | 8 | import { Location } from '../types/Location' 9 | import { TransportMode, TransportSubmode, QueryMode } from '../types/Mode' 10 | 11 | import { convertFeatureToLocation, isValidDate } from '../utils' 12 | 13 | import { createGetFeatures } from '../geocoderLegacy' 14 | 15 | import { Leg } from '../fields/Leg' 16 | 17 | import { 18 | getServiceConfig, 19 | mergeConfig, 20 | ArgumentConfig, 21 | OverrideConfig, 22 | } from '../config' 23 | 24 | /** 25 | * @deprecated 26 | * The JourneyPlanner v2 queries and types are deprecated. 27 | * Write your own GraphQL queries for JourneyPlanner v3. 28 | * Write your own types or use those from JourneyPlannerTypes where applicable. 29 | */ 30 | export interface TripPattern { 31 | /** Total distance for the trip, in meters. */ 32 | distance: number 33 | /** 34 | * This sums the direct durations of each leg. Be careful about using this, 35 | * as it is not equal to the duration between startTime and endTime. 36 | * See the directDuration documentation on Leg. 37 | * */ 38 | directDuration: number 39 | /** Duration of the trip, in seconds. */ 40 | duration: number 41 | /** @deprecated Use expectedEndTime instead */ 42 | endTime: string 43 | /** The expected, realtime adjusted date and time the trip ends. */ 44 | expectedEndTime: string 45 | /** The expected, realtime adjusted date and time the trip starts. */ 46 | expectedStartTime: string 47 | id?: string 48 | legs: Leg[] 49 | /** @deprecated Use expectedStartTime instead */ 50 | startTime: string 51 | /** How far the user has to walk, in meters. */ 52 | walkDistance: number 53 | } 54 | 55 | /** 56 | * @deprecated 57 | * The JourneyPlanner v2 queries and types are deprecated. 58 | * Write your own GraphQL queries for JourneyPlanner v3. 59 | * Write your own types or use those from JourneyPlannerTypes where applicable. 60 | */ 61 | export interface TransportSubmodeParam { 62 | transportMode: TransportMode 63 | transportSubmodes: TransportSubmode[] 64 | } 65 | 66 | /** 67 | * @deprecated 68 | * The JourneyPlanner v2 queries and types are deprecated. 69 | * Write your own GraphQL queries for JourneyPlanner v3. 70 | * Write your own types or use those from JourneyPlannerTypes where applicable. 71 | */ 72 | export interface InputBanned { 73 | lines?: string[] 74 | authorities?: string[] 75 | organisations?: string[] 76 | quays?: string[] 77 | quaysHard?: string[] 78 | serviceJourneys?: string[] 79 | } 80 | 81 | /** 82 | * @deprecated 83 | * The JourneyPlanner v2 queries and types are deprecated. 84 | * Write your own GraphQL queries for JourneyPlanner v3. 85 | * Write your own types or use those from JourneyPlannerTypes where applicable. 86 | */ 87 | export interface InputWhiteListed { 88 | lines?: string[] 89 | authorities?: string[] 90 | organisations?: string[] 91 | } 92 | 93 | /** 94 | * @deprecated 95 | * The JourneyPlanner v2 queries and types are deprecated. 96 | * Write your own GraphQL queries for JourneyPlanner v3. 97 | * Write your own types or use those from JourneyPlannerTypes where applicable. 98 | */ 99 | export interface GetTripPatternsParams { 100 | from: Location 101 | to: Location 102 | allowBikeRental?: boolean 103 | arriveBy?: boolean 104 | limit?: number 105 | maxPreTransitWalkDistance?: number 106 | modes?: QueryMode[] 107 | searchDate?: Date 108 | transportSubmodes?: TransportSubmodeParam[] 109 | useFlex?: boolean 110 | walkSpeed?: number 111 | minimumTransferTime?: number 112 | wheelchairAccessible?: boolean 113 | banned?: InputBanned 114 | whiteListed?: InputWhiteListed 115 | } 116 | 117 | /** 118 | * @deprecated 119 | * The JourneyPlanner v2 queries and types are deprecated. 120 | * Write your own GraphQL queries for JourneyPlanner v3. 121 | * Write your own types or use those from JourneyPlannerTypes where applicable. 122 | */ 123 | interface GetTripPatternsVariables { 124 | from: Location 125 | to: Location 126 | allowBikeRental?: boolean 127 | arriveBy: boolean 128 | numTripPatterns: number 129 | maxPreTransitWalkDistance?: number 130 | modes: QueryMode[] 131 | dateTime: string 132 | transportSubmodes: TransportSubmodeParam[] 133 | useFlex?: boolean 134 | walkSpeed?: number 135 | minimumTransferTime?: number 136 | wheelchair: boolean 137 | banned?: InputBanned 138 | whiteListed?: InputWhiteListed 139 | } 140 | 141 | const DEFAULT_MODES = [ 142 | QueryMode.FOOT, 143 | QueryMode.BUS, 144 | QueryMode.TRAM, 145 | QueryMode.RAIL, 146 | QueryMode.METRO, 147 | QueryMode.WATER, 148 | QueryMode.AIR, 149 | QueryMode.LIFT, 150 | ] 151 | 152 | function getTripPatternsVariables( 153 | params: GetTripPatternsParams, 154 | ): GetTripPatternsVariables { 155 | const { 156 | from, 157 | to, 158 | searchDate = new Date(), 159 | arriveBy = false, 160 | modes = DEFAULT_MODES, 161 | transportSubmodes = [], 162 | wheelchairAccessible = false, 163 | limit = 5, 164 | ...rest 165 | } = params || {} 166 | 167 | return { 168 | ...rest, 169 | from, 170 | to, 171 | dateTime: searchDate.toISOString(), 172 | arriveBy, 173 | modes, 174 | transportSubmodes, 175 | wheelchair: wheelchairAccessible, 176 | numTripPatterns: limit, 177 | } 178 | } 179 | 180 | export function createGetTripPatterns(argConfig: ArgumentConfig) { 181 | const config = getServiceConfig(argConfig) 182 | 183 | return function getTripPatterns( 184 | params: GetTripPatternsParams, 185 | overrideConfig?: OverrideConfig, 186 | options?: RequestOptions, 187 | ): Promise { 188 | return journeyPlannerQuery<{ trip: { tripPatterns: TripPattern[] } }>( 189 | getTripPatternQuery, 190 | getTripPatternsVariables(params), 191 | mergeConfig(config, overrideConfig), 192 | options, 193 | ).then((data) => { 194 | if (!data?.trip?.tripPatterns) { 195 | return [] 196 | } 197 | 198 | return data.trip.tripPatterns.map((trip) => ({ 199 | ...trip, 200 | legs: trip.legs.map(legMapper), 201 | })) 202 | }) 203 | } 204 | } 205 | 206 | export function getTripPatternsQuery(params: GetTripPatternsParams): { 207 | query: string 208 | variables?: { [key: string]: any } 209 | } { 210 | return getGraphqlParams( 211 | getTripPatternQuery, 212 | getTripPatternsVariables(params), 213 | ) 214 | } 215 | 216 | export function createFindTrips(argConfig: ArgumentConfig) { 217 | const getFeatures = createGetFeatures(argConfig) 218 | const getTripPatterns = createGetTripPatterns(argConfig) 219 | 220 | return async function findTrips( 221 | from: string, 222 | to: string, 223 | date?: Date | string | number, 224 | options?: RequestOptions, 225 | ): Promise { 226 | const searchDate = date ? new Date(date) : new Date() 227 | 228 | if (!isValidDate(searchDate)) { 229 | throw new Error( 230 | 'Entur SDK: Could not parse argument to valid Date', 231 | ) 232 | } 233 | 234 | const [fromFeatures, toFeatures] = await Promise.all([ 235 | getFeatures(from), 236 | getFeatures(to), 237 | ]) 238 | 239 | if (!fromFeatures || !fromFeatures.length) { 240 | throw new Error( 241 | `Entur SDK: Could not find any locations matching argument "${from}"`, 242 | ) 243 | } 244 | 245 | if (!toFeatures || !toFeatures.length) { 246 | throw new Error( 247 | `Entur SDK: Could not find any locations matching argument "${to}"`, 248 | ) 249 | } 250 | 251 | return getTripPatterns( 252 | { 253 | from: convertFeatureToLocation(fromFeatures[0]), 254 | to: convertFeatureToLocation(toFeatures[0]), 255 | searchDate, 256 | }, 257 | undefined, 258 | options, 259 | ) 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/trip/mapper.ts: -------------------------------------------------------------------------------- 1 | import { IntermediateEstimatedCall } from '../fields/EstimatedCall' 2 | 3 | import { Authority } from '../fields/Authority' 4 | import { Leg } from '../fields/Leg' 5 | import { Notice } from '../fields/Notice' 6 | 7 | import { uniqBy } from '../utils' 8 | 9 | function getNoticesFromIntermediateEstimatedCalls( 10 | estimatedCalls: IntermediateEstimatedCall[], 11 | ): Notice[] { 12 | if (!estimatedCalls?.length) return [] 13 | return estimatedCalls 14 | .map(({ notices }) => notices || []) 15 | .reduce((a, b) => [...a, ...b], []) 16 | } 17 | 18 | export function getNotices(leg: Leg): Notice[] { 19 | const notices = [ 20 | ...getNoticesFromIntermediateEstimatedCalls( 21 | leg.intermediateEstimatedCalls, 22 | ), 23 | ...(leg.serviceJourney?.notices || []), 24 | ...(leg.serviceJourney?.journeyPattern?.notices || []), 25 | ...(leg.serviceJourney?.journeyPattern?.line?.notices || []), 26 | ...(leg.fromEstimatedCall?.notices || []), 27 | ...(leg.toEstimatedCall?.notices || []), 28 | ...(leg.line?.notices || []), 29 | ] 30 | return uniqBy(notices, (notice) => notice.text) 31 | } 32 | 33 | function authorityMapper(authority?: Authority): Authority | undefined { 34 | if (!authority) return undefined 35 | 36 | return { 37 | id: authority.id, 38 | name: authority.name, 39 | codeSpace: authority.id.split(':')[0], 40 | url: authority.url, 41 | } 42 | } 43 | 44 | export function legMapper(leg: Leg): Leg { 45 | return { 46 | ...leg, 47 | authority: authorityMapper(leg.authority), 48 | notices: getNotices(leg), 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/trip/query.ts: -------------------------------------------------------------------------------- 1 | import { 2 | fragmentName as legFields, 3 | fragments as legFragments, 4 | } from '../fields/Leg' 5 | 6 | const variables = { 7 | numTripPatterns: 'Int!', 8 | from: 'Location!', 9 | to: 'Location!', 10 | dateTime: 'DateTime!', 11 | arriveBy: 'Boolean!', 12 | wheelchair: 'Boolean!', 13 | modes: '[Mode]!', 14 | transportSubmodes: '[TransportSubmodeFilter]', 15 | maxPreTransitWalkDistance: 'Float', 16 | walkSpeed: 'Float', 17 | minimumTransferTime: 'Int', 18 | allowBikeRental: 'Boolean', 19 | useFlex: 'Boolean', 20 | banned: 'InputBanned', 21 | whiteListed: 'InputWhiteListed', 22 | } 23 | 24 | const declaration = Object.entries(variables) 25 | .map(([key, value]) => `$${key}: ${value}`) 26 | .join(',') 27 | 28 | const invocation = Object.keys(variables) 29 | .map((key) => `${key}: $${key}`) 30 | .join(',') 31 | 32 | export const getTripPatternQuery = ` 33 | query (${declaration}) { 34 | trip(${invocation}) { 35 | tripPatterns { 36 | startTime 37 | endTime 38 | expectedStartTime 39 | expectedEndTime 40 | directDuration 41 | duration 42 | distance 43 | walkDistance 44 | legs { 45 | ...${legFields} 46 | } 47 | } 48 | } 49 | } 50 | 51 | ${legFragments.join('')} 52 | ` 53 | -------------------------------------------------------------------------------- /src/types/Coordinates.ts: -------------------------------------------------------------------------------- 1 | export interface Coordinates { 2 | latitude: number 3 | longitude: number 4 | } 5 | -------------------------------------------------------------------------------- /src/types/DestinationDisplay.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @deprecated 3 | * The JourneyPlanner v2 queries and types are deprecated. 4 | * Write your own GraphQL queries for JourneyPlanner v3. 5 | * Write your own types or use those from JourneyPlannerTypes where applicable. 6 | */ 7 | export interface DestinationDisplay { 8 | frontText: string 9 | } 10 | -------------------------------------------------------------------------------- /src/types/Feature.ts: -------------------------------------------------------------------------------- 1 | import { FeatureCategory } from '../constants/featureCategory' 2 | 3 | /** 4 | * @deprecated Use the geocoder.autocomplete and geocoder.reverse methods instead and related types. 5 | */ 6 | export type Feature = { 7 | geometry: { 8 | coordinates: [number, number] // longitude, latitude 9 | type: 'Point' 10 | } 11 | properties: { 12 | id: string 13 | name: string 14 | label?: string 15 | borough: string 16 | accuracy: 'point' 17 | layer: 'venue' | 'address' 18 | borough_gid: string 19 | category: FeatureCategory[] 20 | country_gid: string 21 | county: string 22 | county_gid: string 23 | gid: string 24 | housenumber?: string 25 | locality: string 26 | locality_gid: string 27 | postalcode: string 28 | source: string 29 | source_id: string 30 | street: string 31 | tariff_zones?: string[] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/types/FlexibleLineType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @deprecated 3 | * The JourneyPlanner v2 queries and types are deprecated. 4 | * Write your own GraphQL queries for JourneyPlanner v3. 5 | * Write your own types or use those from JourneyPlannerTypes where applicable. 6 | */ 7 | export type FlexibleLineType = 8 | | 'corridorService' 9 | | 'mainRouteWithFlexibleEnds' 10 | | 'flexibleAreasOnly' 11 | | 'hailAndRideSections' 12 | | 'fixedStopAreaWide' 13 | | 'freeAreaAreaWide' 14 | | 'mixedFlexible' 15 | | 'mixedFlexibleAndFixed' 16 | | 'fixed' 17 | | 'other' 18 | -------------------------------------------------------------------------------- /src/types/Location.ts: -------------------------------------------------------------------------------- 1 | import { Coordinates } from './Coordinates' 2 | 3 | /** 4 | * @deprecated 5 | * The JourneyPlanner v2 queries and types are deprecated. 6 | * Write your own GraphQL queries for JourneyPlanner v3. 7 | * Write your own types or use those from JourneyPlannerTypes where applicable. 8 | */ 9 | export interface Location { 10 | name?: string 11 | place?: string 12 | coordinates?: Coordinates 13 | } 14 | -------------------------------------------------------------------------------- /src/types/Mode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @deprecated 3 | * The JourneyPlanner v2 queries and types are deprecated. 4 | * Write your own GraphQL queries for JourneyPlanner v3. 5 | * Write your own types or use those from JourneyPlannerTypes where applicable. 6 | */ 7 | export enum TransportMode { 8 | AIR = 'air', 9 | BUS = 'bus', 10 | CABLEWAY = 'cableway', 11 | CAR = 'car', 12 | COACH = 'coach', 13 | FUNICULAR = 'funicular', 14 | LIFT = 'lift', 15 | METRO = 'metro', 16 | RAIL = 'rail', 17 | TRAM = 'tram', 18 | WATER = 'water', 19 | } 20 | 21 | /** 22 | * @deprecated 23 | * The JourneyPlanner v2 queries and types are deprecated. 24 | * Write your own GraphQL queries for JourneyPlanner v3. 25 | * Write your own types or use those from JourneyPlannerTypes where applicable. 26 | */ 27 | export enum TransportSubmode { 28 | AIRPORT_LINK_BUS = 'airportLinkBus', 29 | AIRPORT_LINK_RAIL = 'airportLinkRail', 30 | CITY_TRAM = 'cityTram', 31 | DOMESTIC_FLIGHT = 'domesticFlight', 32 | EXPRESS_BUS = 'expressBus', 33 | FUNICULAR = 'funicular', 34 | HELICOPTER_SERVICE = 'helicopterService', 35 | HIGH_SPEED_PASSENGER_SERVICE = 'highSpeedPassengerService', 36 | HIGH_SPEED_VEHICLE_SERVICE = 'highSpeedVehicleService', 37 | INTERNATIONAL = 'international', 38 | INTERNATIONAL_CAR_FERRY = 'internationalCarFerry', 39 | INTERNATIONAL_COACH = 'internationalCoach', 40 | INTERNATIONAL_FLIGHT = 'internationalFlight', 41 | INTERNATIONAL_PASSENGER_FERRY = 'internationalPassengerFerry', 42 | INTERREGIONAL_RAIL = 'interregionalRail', 43 | LOCAL = 'local', 44 | LOCAL_BUS = 'localBus', 45 | LOCAL_CAR_FERRY = 'localCarFerry', 46 | LOCAL_PASSENGER_FERRY = 'localPassengerFerry', 47 | LOCAL_TRAM = 'localTram', 48 | LONG_DISTANCE = 'longDistance', 49 | METRO = 'metro', 50 | NATIONAL_CAR_FERRY = 'nationalCarFerry', 51 | NATIONAL_COACH = 'nationalCoach', 52 | NIGHT_BUS = 'nightBus', 53 | NIGHT_RAIL = 'nightRail', 54 | RAIL_REPLACEMENT_BUS = 'railReplacementBus', 55 | REGIONAL_BUS = 'regionalBus', 56 | REGIONAL_CAR_FERRY = 'regionalCarFerry', 57 | REGIONAL_RAIL = 'regionalRail', 58 | SCHOOL_BUS = 'schoolBus', 59 | SHUTTLE_BUS = 'shuttleBus', 60 | SIGHTSEEING_BUS = 'sightseeingBus', 61 | SIGHTSEEING_SERVICE = 'sightseeingService', 62 | TELECABIN = 'telecabin', 63 | TOURIST_RAILWAY = 'touristRailway', 64 | } 65 | 66 | /** 67 | * @deprecated 68 | * The JourneyPlanner v2 queries and types are deprecated. 69 | * Write your own GraphQL queries for JourneyPlanner v3. 70 | * Write your own types or use those from JourneyPlannerTypes where applicable. 71 | */ 72 | export enum LegMode { 73 | AIR = 'air', 74 | BICYCLE = 'bicycle', 75 | BUS = 'bus', 76 | CABLEWAY = 'cableway', 77 | CAR = 'car', 78 | COACH = 'coach', 79 | FOOT = 'foot', 80 | FUNICULAR = 'funicular', 81 | LIFT = 'lift', 82 | METRO = 'metro', 83 | RAIL = 'rail', 84 | TRAM = 'tram', 85 | WATER = 'water', 86 | } 87 | 88 | /** 89 | * @deprecated 90 | * The JourneyPlanner v2 queries and types are deprecated. 91 | * Write your own GraphQL queries for JourneyPlanner v3. 92 | * Write your own types or use those from JourneyPlannerTypes where applicable. 93 | */ 94 | export enum QueryMode { 95 | AIR = 'air', 96 | BICYCLE = 'bicycle', 97 | BUS = 'bus', 98 | CABLEWAY = 'cableway', 99 | CAR = 'car', 100 | CAR_DROPOFF = 'car_dropoff', 101 | CAR_PARK = 'car_park', 102 | CAR_PICKUP = 'car_pickup', 103 | COACH = 'coach', 104 | FOOT = 'foot', 105 | FUNICULAR = 'funicular', 106 | LIFT = 'lift', 107 | METRO = 'metro', 108 | RAIL = 'rail', 109 | TRAM = 'tram', 110 | TRANSIT = 'transit', 111 | WATER = 'water', 112 | } 113 | -------------------------------------------------------------------------------- /src/types/MultilingualString.ts: -------------------------------------------------------------------------------- 1 | export interface MultilingualString { 2 | lang: 'eng' | 'nob' | 'nno' 3 | language?: 'en' | 'nb' | 'nn' | 'no' 4 | value: string 5 | } 6 | -------------------------------------------------------------------------------- /src/types/StopPlace.ts: -------------------------------------------------------------------------------- 1 | import { MultilingualString } from './MultilingualString' 2 | import { TransportMode, TransportSubmode } from '../types/Mode' 3 | 4 | import { Quay } from '../fields/Quay' 5 | 6 | /** 7 | * @deprecated 8 | * The JourneyPlanner v2 queries and types are deprecated. 9 | * Write your own GraphQL queries for JourneyPlanner v3. 10 | * Write your own types or use those from JourneyPlannerTypes where applicable. 11 | */ 12 | export interface StopPlaceDetails { 13 | id: string 14 | name: string 15 | description?: string 16 | latitude: number 17 | longitude: number 18 | wheelchairBoarding: 'noInformation' | 'possible' | 'notPossible' 19 | weighting: 20 | | 'preferredInterchange' 21 | | 'recommendedInterchange' 22 | | 'interchangeAllowed' 23 | | 'noInterchange' 24 | transportMode: TransportMode 25 | transportSubmode?: TransportSubmode 26 | quays?: Quay[] 27 | } 28 | 29 | /** 30 | * @deprecated 31 | * The JourneyPlanner v2 queries and types are deprecated. 32 | * Write your own GraphQL queries for JourneyPlanner v3. 33 | * Write your own types or use those from JourneyPlannerTypes where applicable. 34 | */ 35 | export type LimitationStatusType = 'FALSE' | 'TRUE' | 'PARTIAL' | 'UNKNOWN' 36 | 37 | /** 38 | * @deprecated 39 | * The JourneyPlanner v2 queries and types are deprecated. 40 | * Write your own GraphQL queries for JourneyPlanner v3. 41 | * Write your own types or use those from JourneyPlannerTypes where applicable. 42 | */ 43 | export type WaitingRoomEquipment = { 44 | id: string 45 | } 46 | 47 | /** 48 | * @deprecated 49 | * The JourneyPlanner v2 queries and types are deprecated. 50 | * Write your own GraphQL queries for JourneyPlanner v3. 51 | * Write your own types or use those from JourneyPlannerTypes where applicable. 52 | */ 53 | export type ShelterEquipment = { 54 | id: string 55 | } 56 | 57 | /** 58 | * @deprecated 59 | * The JourneyPlanner v2 queries and types are deprecated. 60 | * Write your own GraphQL queries for JourneyPlanner v3. 61 | * Write your own types or use those from JourneyPlannerTypes where applicable. 62 | */ 63 | export type SanitaryEquipment = { 64 | id: string 65 | numberOfToilets: number 66 | gender: 'both' | 'femaleOnly' | 'maleOnly' | 'sameSexOnly' 67 | } 68 | 69 | /** 70 | * @deprecated 71 | * The JourneyPlanner v2 queries and types are deprecated. 72 | * Write your own GraphQL queries for JourneyPlanner v3. 73 | * Write your own types or use those from JourneyPlannerTypes where applicable. 74 | */ 75 | export type TicketingEquipment = { 76 | id: string 77 | ticketOffice: boolean 78 | ticketMachines: boolean 79 | numberOfMachines: number 80 | } 81 | 82 | /** 83 | * @deprecated 84 | * The JourneyPlanner v2 queries and types are deprecated. 85 | * Write your own GraphQL queries for JourneyPlanner v3. 86 | * Write your own types or use those from JourneyPlannerTypes where applicable. 87 | */ 88 | export type ParkingVehicle = 89 | | 'pedalCycle' 90 | | 'moped' 91 | | 'motorcycle' 92 | | 'motorcycleWithSidecar' 93 | | 'motorScooter' 94 | | 'twoWheeledVehicle' 95 | | 'threeWheeledVehicle' 96 | | 'car' 97 | | 'smallCar' 98 | | 'passengerCar' 99 | | 'largeCar' 100 | | 'fourWheelDrive' 101 | | 'taxi' 102 | | 'camperCar' 103 | | 'carWithTrailer' 104 | | 'carWithCaravan' 105 | | 'minibus' 106 | | 'bus' 107 | | 'van' 108 | | 'largeVan' 109 | | 'highSidedVehicle' 110 | | 'lightGoodsVehicle' 111 | | 'heavyGoodsVehicle' 112 | | 'truck' 113 | | 'agriculturalVehicle' 114 | | 'tanker' 115 | | 'tram' 116 | | 'articulatedVehicle' 117 | | 'vehicleWithTrailer' 118 | | 'lightGoodsVehicleWithTrailer' 119 | | 'heavyGoodsVehicleWithTrailer' 120 | | 'undefined' 121 | | 'other' 122 | | 'allPassengerVehicles' 123 | | 'all' 124 | 125 | /** 126 | * @deprecated 127 | * The JourneyPlanner v2 queries and types are deprecated. 128 | * Write your own GraphQL queries for JourneyPlanner v3. 129 | * Write your own types or use those from JourneyPlannerTypes where applicable. 130 | */ 131 | export interface StopPlaceFacilitiesStopPlace { 132 | id: string 133 | name: MultilingualString 134 | accessibilityAssessment: { 135 | limitations: { 136 | wheelchairAccess: LimitationStatusType 137 | stepFreeAccess: LimitationStatusType 138 | } 139 | } 140 | placeEquipments: { 141 | waitingRoomEquipment?: WaitingRoomEquipment[] 142 | shelterEquipment?: ShelterEquipment[] 143 | sanitaryEquipment?: SanitaryEquipment[] 144 | ticketingEquipment?: TicketingEquipment[] 145 | } 146 | } 147 | 148 | /** 149 | * @deprecated 150 | * The JourneyPlanner v2 queries and types are deprecated. 151 | * Write your own GraphQL queries for JourneyPlanner v3. 152 | * Write your own types or use those from JourneyPlannerTypes where applicable. 153 | */ 154 | export interface StopPlaceFacilitiesParking { 155 | name: MultilingualString 156 | parentSiteRef: string 157 | totalCapacity?: number 158 | principalCapacity?: number 159 | parkingVehicleTypes?: ParkingVehicle[] 160 | } 161 | 162 | /** 163 | * @deprecated 164 | * The JourneyPlanner v2 queries and types are deprecated. 165 | * Write your own GraphQL queries for JourneyPlanner v3. 166 | * Write your own types or use those from JourneyPlannerTypes where applicable. 167 | */ 168 | export interface StopPlaceFacilities { 169 | stopPlace: StopPlaceFacilitiesStopPlace[] 170 | parking: StopPlaceFacilitiesParking[] 171 | } 172 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { point, lineString } from '@turf/helpers' 2 | import bbox from '@turf/bbox' 3 | import destination from '@turf/destination' 4 | import PromiseThrottle from 'promise-throttle' 5 | import { Feature } from './types/Feature' 6 | import { Location } from './types/Location' 7 | import { Coordinates } from './types/Coordinates' 8 | import { 9 | MAX_CALLS_PER_SECOND, 10 | MAX_CALLS_PER_MINUTE, 11 | } from './constants/rateLimits' 12 | 13 | export function convertFeatureToLocation(feature: Feature): Location { 14 | const { properties, geometry } = feature 15 | 16 | return { 17 | name: properties.name, 18 | place: properties.id, 19 | coordinates: { 20 | latitude: geometry.coordinates[1], 21 | longitude: geometry.coordinates[0], 22 | }, 23 | } 24 | } 25 | 26 | interface Bbox { 27 | minLng: number 28 | minLat: number 29 | maxLng: number 30 | maxLat: number 31 | } 32 | 33 | export function convertPositionToBbox( 34 | coordinates: Coordinates, 35 | distance: number, 36 | ): Bbox { 37 | const { latitude, longitude } = coordinates 38 | const distanceToKilometer = distance / 1000 39 | 40 | const position = point([longitude, latitude]) 41 | 42 | const east = destination(position, distanceToKilometer, 0) 43 | const north = destination(position, distanceToKilometer, 90) 44 | const west = destination(position, distanceToKilometer, 180) 45 | const south = destination(position, distanceToKilometer, -90) 46 | 47 | const line = lineString([ 48 | east.geometry.coordinates, 49 | north.geometry.coordinates, 50 | west.geometry.coordinates, 51 | south.geometry.coordinates, 52 | ]) 53 | 54 | const [minLng, minLat, maxLng, maxLat] = bbox(line) 55 | 56 | return { 57 | minLng, 58 | minLat, 59 | maxLng, 60 | maxLat, 61 | } 62 | } 63 | 64 | export function throttler( 65 | func: (arg: T) => Promise, 66 | args: T[], 67 | ): Promise { 68 | const argCount = args.length 69 | 70 | const requestsPerSecond = 71 | argCount > MAX_CALLS_PER_MINUTE 72 | ? Math.floor(MAX_CALLS_PER_MINUTE / 60) 73 | : MAX_CALLS_PER_SECOND 74 | 75 | const promiseThrottle = new PromiseThrottle({ requestsPerSecond }) 76 | return Promise.all(args.map((a) => promiseThrottle.add(() => func(a)))) 77 | } 78 | 79 | export function isValidDate(d: any): boolean { 80 | return ( 81 | Object.prototype.toString.call(d) === '[object Date]' && 82 | !Number.isNaN(d.getTime()) 83 | ) 84 | } 85 | 86 | export function uniqBy(arr: T[], getKey: (arg: T) => K): T[] { 87 | return [ 88 | ...arr 89 | .reduce((map, item) => { 90 | const key = getKey(item) 91 | 92 | if (!map.has(key)) { 93 | map.set(key, item) 94 | } 95 | 96 | return map 97 | }, new Map()) 98 | .values(), 99 | ] 100 | } 101 | 102 | function identity(thing: T): T { 103 | return thing 104 | } 105 | 106 | export function uniq(arr: T[]): T[] { 107 | return uniqBy(arr, identity) 108 | } 109 | 110 | export function forceOrder( 111 | list: T[], 112 | sequence: V[], 113 | getKey: (arg: T) => V, 114 | ): Array { 115 | let queue = [...list] 116 | const result: Array = [] 117 | 118 | const getKeyFunc = getKey || identity 119 | 120 | sequence.forEach((sequenceIdentifier) => { 121 | const item = queue.find((t) => getKeyFunc(t) === sequenceIdentifier) 122 | if (item) { 123 | result.push(item) 124 | queue = queue.filter((q) => q !== item) 125 | } else { 126 | result.push(undefined) 127 | } 128 | }) 129 | 130 | return result 131 | } 132 | 133 | type Falsy = void | null | undefined | 0 | '' | false 134 | 135 | type Truthy = Exclude 136 | 137 | export function isTruthy(thing: T): thing is Truthy { 138 | return Boolean(thing) 139 | } 140 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const [, , nameOfMethodToRun] = process.argv 2 | 3 | require('@babel/register')({ 4 | presets: [ 5 | [ 6 | '@babel/preset-env', 7 | { 8 | targets: { node: 'current' }, 9 | }, 10 | ], 11 | ], 12 | ignore: [/node_modules/], 13 | }) 14 | 15 | const MOMOEN_LOCATION = { 16 | name: 'Momoen', 17 | place: 'NSR:StopPlace:3867', 18 | coordinates: { 19 | latitude: 59.804883, 20 | longitude: 11.391951, 21 | }, 22 | } 23 | 24 | const LILLESTROM_LEGESENTER_LOCATION = { 25 | name: 'Lillestrøm legesenter', 26 | place: 'OSM:TopographicPlace:5921083879', 27 | coordinates: { 28 | latitude: 59.962737, 29 | longitude: 11.062501, 30 | }, 31 | } 32 | 33 | const JERNBANETORGET_BIKE_STOP = '272' 34 | 35 | const LILLEHAMMER_STASJON = 'NSR:StopPlace:420' 36 | const HAMAR_STASJON = 'NSR:StopPlace:219' 37 | const JERNBANETORGET = 'NSR:StopPlace:58366' 38 | const OSLO_S = 'NSR:StopPlace:59872' 39 | const MISSING = 'NSR:StopPlace:5483957348574389' 40 | 41 | const { default: createEnturClient } = require('./src') 42 | 43 | const client = createEnturClient({ 44 | clientName: 'SDK cli-test', 45 | }) 46 | 47 | function getMethodToRun(name) { 48 | switch (name) { 49 | case 'getTripPatterns': 50 | return client.getTripPatterns( 51 | MOMOEN_LOCATION, 52 | LILLESTROM_LEGESENTER_LOCATION, 53 | undefined, 54 | [], 55 | ) 56 | case 'getStopPlace': 57 | return client.getStopPlace(JERNBANETORGET) 58 | case 'getStopPlaces': 59 | return client.getStopPlaces([ 60 | OSLO_S, 61 | MISSING, 62 | JERNBANETORGET, 63 | HAMAR_STASJON, 64 | LILLEHAMMER_STASJON, 65 | ]) 66 | case 'getQuaysFromStopPlace': 67 | return client.getQuaysFromStopPlace(JERNBANETORGET) 68 | case 'getBikeRentalStation': 69 | return client.getBikeRentalStation(JERNBANETORGET_BIKE_STOP) 70 | case 'getBikeRentalStationsByPosition': 71 | return client.getBikeRentalStationsByPosition( 72 | { 73 | latitude: 59.911898, 74 | longitude: 10.75038, 75 | }, 76 | 50, 77 | ) 78 | case 'getDeparturesFromStopPlace': 79 | return client.getDeparturesFromStopPlace(JERNBANETORGET) 80 | case 'getDeparturesFromStopPlaces': 81 | return client.getDeparturesFromStopPlaces( 82 | [JERNBANETORGET, HAMAR_STASJON, LILLEHAMMER_STASJON], 83 | { limit: 2 }, 84 | ) 85 | case 'getDeparturesBetweenStopPlaces': 86 | return client.getDeparturesBetweenStopPlaces( 87 | LILLEHAMMER_STASJON, 88 | HAMAR_STASJON, 89 | ) 90 | default: 91 | // eslint-disable-next-line prefer-promise-reject-errors 92 | return Promise.reject(`Error: method ${name} is not defined`) 93 | } 94 | } 95 | 96 | getMethodToRun(nameOfMethodToRun) 97 | // eslint-disable-next-line no-console 98 | .then( 99 | (data) => console.log(JSON.stringify(data, undefined, 4)), 100 | console.error, 101 | ) 102 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "moduleResolution": "node", 5 | "allowJs": true, 6 | "emitDeclarationOnly": true, 7 | "declarationDir": "lib", 8 | "declaration": true, 9 | "strict": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "typeRoots": ["./node_modules/@types", "./typings"] 13 | }, 14 | "include": ["src"] 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "moduleResolution": "node", 5 | "allowJs": true, 6 | "strict": true, 7 | "isolatedModules": true, 8 | "esModuleInterop": true, 9 | "typeRoots": ["./node_modules/@types", "./typings"] 10 | }, 11 | "include": ["src"] 12 | } 13 | --------------------------------------------------------------------------------