├── .eslintignore ├── .eslintrc.json ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── blunderbuss.yml ├── in-solidarity.yml ├── sync-repo-settings.yaml └── workflows │ ├── build-demos.yml │ ├── codeql.yml │ ├── dependabot.yml │ ├── docs.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc.json ├── .releaserc ├── DEVELOPMENT-README.md ├── LICENSE ├── README.md ├── SECURITY.md ├── appengine ├── README.md ├── app.yaml ├── deploy.sh ├── fleet-debugger.sh └── package.json ├── cloud-run ├── README.md ├── deploy.sh ├── fleet-debugger.sh └── package.json ├── components ├── auth.js ├── bigqueryDS.js ├── browser.js ├── cloudLoggingDS.js ├── datasource.js ├── fleetArchiveDS.js ├── logging.js └── serve.js ├── datasets ├── fleet_archive_delivery.json ├── fleet_archive_multitrip.json ├── jump-demo.json ├── lmfs.json └── two-trips-bay-area.json ├── demos ├── README.md ├── jump │ ├── asset-manifest.json │ ├── data.json │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ ├── robots.txt │ ├── static │ │ ├── css │ │ │ ├── main.fabf1335.css │ │ │ └── main.fabf1335.css.map │ │ └── js │ │ │ ├── main.03d2a252.js │ │ │ ├── main.03d2a252.js.LICENSE.txt │ │ │ └── main.03d2a252.js.map │ └── view-bundle.sh ├── lmfs │ ├── asset-manifest.json │ ├── data.json │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ ├── robots.txt │ ├── static │ │ ├── css │ │ │ ├── main.fabf1335.css │ │ │ └── main.fabf1335.css.map │ │ └── js │ │ │ ├── main.03d2a252.js │ │ │ ├── main.03d2a252.js.LICENSE.txt │ │ │ └── main.03d2a252.js.map │ └── view-bundle.sh ├── multiple-trips │ ├── asset-manifest.json │ ├── data.json │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ ├── robots.txt │ ├── static │ │ ├── css │ │ │ ├── main.fabf1335.css │ │ │ └── main.fabf1335.css.map │ │ └── js │ │ │ ├── main.03d2a252.js │ │ │ ├── main.03d2a252.js.LICENSE.txt │ │ │ └── main.03d2a252.js.map │ └── view-bundle.sh └── rebuild.sh ├── docs ├── CONTRIBUTING.md ├── DwellTimes.md ├── GPSAccuracy.md ├── Heading.md ├── MissingUpdates.md ├── PRIVACY.md ├── PlannedPaths.md ├── ReplaceVehicleMovement.md ├── Speed.md ├── Tasks.md ├── VelocityJumps.md ├── code-of-conduct.md ├── reporting-issues.md └── screenshots │ ├── Fleet_Engine_Logs_Loading.png │ ├── dwelltime.png │ ├── fleetdebugger.png │ ├── gps_accuracy.png │ ├── heading.png │ ├── planned_paths.png │ ├── speed.png │ ├── tasks.png │ └── vehiclereplay.gif ├── dune-buggy.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.js ├── App.test.js ├── CloudLogging.js ├── CloudLogging.test.js ├── Dataframe.js ├── ExtraDataSource.js ├── HighVelocityJump.js ├── LogTable.js ├── Map.js ├── MissingUpdate.js ├── PolylineCreation.js ├── ServeHome.js ├── Stats.js ├── Task.js ├── TaskLogs.js ├── TaskLogs.test.js ├── TimeSlider.js ├── ToggleBar.js ├── TrafficPolyline.js ├── TrafficPolyline.test.js ├── Trip.js ├── TripLogs.js ├── TripLogs.test.js ├── TripObjects.js ├── Utils.js ├── constants.js ├── global.css ├── index.js ├── localStorage.js ├── localStorage.test.js ├── logo.svg ├── queryString.js ├── setupTests.js └── vehicleData.js └── utils ├── clean-demos.js └── view-bundle.sh /.eslintignore: -------------------------------------------------------------------------------- 1 | bazel-* 2 | coverage/ 3 | dist/ 4 | docs/ 5 | lib/ 6 | node_modules/ 7 | public/ 8 | build/ 9 | demos/ 10 | datasets/ 11 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:react/recommended", 5 | "plugin:react/jsx-runtime", 6 | "plugin:prettier/recommended" 7 | ], 8 | "parser": "@babel/eslint-parser", 9 | "parserOptions": { 10 | "ecmaVersion": 2021, 11 | "sourceType": "module", 12 | "ecmaFeatures": { 13 | "jsx": true 14 | }, 15 | "requireConfigFile": false, 16 | "babelOptions": { 17 | "presets": ["@babel/preset-react"], 18 | "plugins": ["@babel/plugin-proposal-class-properties"] 19 | } 20 | }, 21 | "plugins": ["react", "jest"], 22 | "rules": { 23 | "no-var": "error", 24 | "no-unused-vars": ["warn", { 25 | "argsIgnorePattern": "^_", 26 | "varsIgnorePattern": "^_" 27 | }], 28 | "prefer-arrow-callback": "error", 29 | "react/prop-types": "off", 30 | "react/jsx-key": "off", 31 | "react/display-name": "off", 32 | "prettier/prettier": "warn" 33 | }, 34 | "globals": { 35 | "google": "readonly" 36 | }, 37 | "env": { 38 | "browser": true, 39 | "node": true, 40 | "es6": true, 41 | "jest/globals": true 42 | }, 43 | "settings": { 44 | "react": { 45 | "version": "detect" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners 16 | 17 | * @googlemaps/eng 18 | 19 | .github/ @googlemaps/process 20 | .releaserc @googlemaps/process 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Expected Behavior 2 | 3 | ## Actual Behavior 4 | 5 | ## Steps to Reproduce the Problem 6 | 7 | 1. 8 | 1. 9 | 1. 10 | 11 | ## Photo (good) and/or Video (best) file attached that shows the issue 12 | 13 | url, or attachment 14 | 15 | ## Zipped file of your cloud logging data that shows the issue. 16 | 17 | url, or attachment (search and replace sensitive strings, for instance vehicle ID or trip ID) 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | > It's a good idea to open an issue first for discussion. 4 | 5 | - [ ] Tests pass 6 | - [ ] Appropriate changes to README are included in PR 7 | -------------------------------------------------------------------------------- /.github/blunderbuss.yml: -------------------------------------------------------------------------------- 1 | assign_issues: 2 | - regeter 3 | assign_prs: 4 | - regeter -------------------------------------------------------------------------------- /.github/in-solidarity.yml: -------------------------------------------------------------------------------- 1 | rules: 2 | master: 3 | level: warning 4 | slave: 5 | level: failure 6 | whitelist: 7 | level: failure 8 | blacklist: 9 | level: failure 10 | ignore: 11 | - dist/ 12 | - demos/ 13 | - .github/ -------------------------------------------------------------------------------- /.github/sync-repo-settings.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # https://github.com/googleapis/repo-automation-bots/tree/main/packages/sync-repo-settings 16 | 17 | rebaseMergeAllowed: true 18 | squashMergeAllowed: true 19 | mergeCommitAllowed: false 20 | deleteBranchOnMerge: true 21 | branchProtectionRules: 22 | - pattern: main 23 | isAdminEnforced: false 24 | requiresStrictStatusChecks: false 25 | requiredStatusCheckContexts: 26 | - 'cla/google' 27 | - 'test' 28 | - 'snippet-bot check' 29 | - 'header-check' 30 | requiredApprovingReviewCount: 1 31 | requiresCodeOwnerReviews: true 32 | - pattern: master 33 | isAdminEnforced: false 34 | requiresStrictStatusChecks: false 35 | requiredStatusCheckContexts: 36 | - 'cla/google' 37 | - 'test' 38 | - 'snippet-bot check' 39 | - 'header-check' 40 | requiredApprovingReviewCount: 1 41 | requiresCodeOwnerReviews: true 42 | permissionRules: 43 | - team: admin 44 | permission: admin 45 | -------------------------------------------------------------------------------- /.github/workflows/build-demos.yml: -------------------------------------------------------------------------------- 1 | name: Build Demos and push to main 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths: 7 | - 'src/**' 8 | - 'public/**' 9 | - 'datasets/**' 10 | - 'package.json' 11 | - 'package-lock.json' 12 | workflow_dispatch: # manual trigger when needed 13 | 14 | permissions: 15 | contents: write 16 | 17 | jobs: 18 | build-and-deploy: 19 | if: github.repository == 'googlemaps/fleet-debugger' 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@v4 25 | with: 26 | ssh-key: ${{ secrets.DEPLOY_KEY }} 27 | 28 | - name: Setup Node.js 29 | uses: actions/setup-node@v3 30 | with: 31 | node-version: '16' 32 | cache: 'npm' 33 | 34 | - name: Install dependencies 35 | run: npm install 36 | 37 | - name: Build and Update Demos 38 | env: 39 | CI: false 40 | run: | 41 | # Build Jump demo 42 | echo "Building Jump demo..." 43 | rm -rf demos/jump 44 | cp datasets/jump-demo.json public/data.json 45 | npm run build 46 | cp -r build demos/jump 47 | 48 | # Build LMFS demo 49 | echo "Building LMFS demo..." 50 | rm -rf demos/lmfs 51 | cp datasets/lmfs.json public/data.json 52 | npm run build 53 | cp -r build demos/lmfs 54 | 55 | # Build Multiple Trips demo 56 | echo "Building Multiple Trips demo..." 57 | rm -rf demos/multiple-trips 58 | cp datasets/two-trips-bay-area.json public/data.json 59 | npm run build 60 | cp -r build demos/multiple-trips 61 | 62 | - name: Commit and Push Changes 63 | run: | 64 | echo "Configuring git..." 65 | git config --local user.email "github-actions[bot]@users.noreply.github.com" 66 | git config --local user.name "github-actions[bot]" 67 | 68 | echo "Stashing changes..." 69 | git stash 70 | 71 | echo "Pulling latest changes..." 72 | git pull origin main --rebase 73 | 74 | echo "Applying stashed changes..." 75 | git stash pop || true 76 | 77 | echo "Adding and committing changes..." 78 | git add demos/ 79 | git commit -m "Update demo builds" || echo "No changes to commit" 80 | 81 | echo "Pushing changes..." 82 | git push 83 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # For most projects, this workflow file will not need changing; you simply need 16 | # to commit it to your repository. 17 | # 18 | # You may wish to alter this file to override the set of languages analyzed, 19 | # or to provide custom queries or build logic. 20 | # 21 | # ******** NOTE ******** 22 | # We have attempted to detect the languages in your repository. Please check 23 | # the `language` matrix defined below to confirm you have the correct set of 24 | # supported CodeQL languages. 25 | # 26 | name: "CodeQL" 27 | 28 | on: 29 | push: 30 | branches: [main] 31 | pull_request: 32 | # The branches below must be a subset of the branches above 33 | branches: [main] 34 | schedule: 35 | - cron: "0 13 * * *" 36 | 37 | jobs: 38 | analyze: 39 | name: Analyze 40 | runs-on: ubuntu-latest 41 | permissions: 42 | actions: read 43 | contents: read 44 | security-events: write 45 | 46 | strategy: 47 | fail-fast: false 48 | matrix: 49 | language: ["javascript"] 50 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 51 | # Learn more: 52 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 53 | 54 | steps: 55 | - name: Checkout repository 56 | uses: actions/checkout@v4 57 | 58 | # Initializes the CodeQL tools for scanning. 59 | - name: Initialize CodeQL 60 | uses: github/codeql-action/init@v2 61 | with: 62 | languages: ${{ matrix.language }} 63 | # If you wish to specify custom queries, you can do so here or in a config file. 64 | # By default, queries listed here will override any specified in a config file. 65 | # Prefix the list here with "+" to use these queries and those in the config file. 66 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 67 | 68 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 69 | # If this step fails, then you should remove it and run the build manually (see below) 70 | - name: Autobuild 71 | uses: github/codeql-action/autobuild@v2 72 | 73 | # ℹ️ Command-line programs to run using the OS shell. 74 | # 📚 https://git.io/JvXDl 75 | 76 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 77 | # and modify them (or add more) to build your code if your project 78 | # uses a compiled language 79 | 80 | #- run: | 81 | # make bootstrap 82 | # make release 83 | 84 | - name: Perform CodeQL Analysis 85 | uses: github/codeql-action/analyze@v2 86 | -------------------------------------------------------------------------------- /.github/workflows/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Dependabot 16 | on: pull_request 17 | 18 | permissions: 19 | contents: write 20 | 21 | jobs: 22 | dependabot: 23 | runs-on: ubuntu-latest 24 | if: ${{ github.actor == 'dependabot[bot]' }} 25 | env: 26 | PR_URL: ${{github.event.pull_request.html_url}} 27 | GITHUB_TOKEN: ${{secrets.SYNCED_GITHUB_TOKEN_REPO}} 28 | steps: 29 | - name: approve 30 | run: gh pr review --approve "$PR_URL" 31 | - name: merge 32 | run: gh pr merge --auto --squash --delete-branch "$PR_URL" 33 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Docs 16 | on: [push, pull_request] 17 | jobs: 18 | test: 19 | permissions: 20 | contents: write 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: actions/cache@v4 25 | with: 26 | path: ~/.npm 27 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 28 | restore-keys: | 29 | ${{ runner.os }}-node- 30 | - run: | 31 | npm i 32 | npm run docs 33 | - uses: peaceiris/actions-gh-pages@v4 34 | if: github.ref == 'refs/heads/main' 35 | with: 36 | github_token: ${{ secrets.GITHUB_TOKEN }} 37 | publish_dir: ./ 38 | user_name: "googlemaps-bot" 39 | user_email: "googlemaps-bot@users.noreply.github.com" 40 | commit_message: ${{ github.event.head_commit.message }} 41 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Release 16 | on: 17 | push: 18 | branches: 19 | - main 20 | concurrency: release 21 | jobs: 22 | build: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | with: 28 | token: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }} 29 | - uses: actions/cache@v4 30 | with: 31 | path: ~/.npm 32 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 33 | restore-keys: | 34 | ${{ runner.os }}-node- 35 | - name: Install dependencies 36 | run: npm install 37 | - name: Lint 38 | run: npm run lint 39 | - name: Test 40 | run: npm test 41 | - name: Release 42 | uses: cycjimmy/semantic-release-action@v3 43 | with: 44 | semantic_version: 19.0.5 45 | extra_plugins: | 46 | @semantic-release/commit-analyzer@5.0.0 47 | semantic-release-interval 48 | @semantic-release/release-notes-generator@10.0.0 49 | @semantic-release/git 50 | @semantic-release/github@8.0.0 51 | @semantic-release/npm@9.0.0 52 | @googlemaps/semantic-release-config 53 | semantic-release-npm-deprecate 54 | env: 55 | GH_TOKEN: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }} 56 | NPM_TOKEN: ${{ secrets.NPM_WOMBAT_TOKEN }} 57 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Test 16 | on: [push, pull_request] 17 | jobs: 18 | test: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: actions/cache@v4 23 | with: 24 | path: ~/.npm 25 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 26 | restore-keys: | 27 | ${{ runner.os }}-node- 28 | - name: Install dependencies 29 | run: npm install 30 | - name: Lint 31 | run: npm run lint 32 | - name: Test 33 | run: npm test 34 | - uses: codecov/codecov-action@v5 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # vim swap files 19 | .swo 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | *.lcov 27 | 28 | # nyc test coverage 29 | .nyc_output 30 | 31 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 32 | .grunt 33 | 34 | # Bower dependency directory (https://bower.io/) 35 | bower_components 36 | 37 | # node-waf configuration 38 | .lock-wscript 39 | 40 | # Compiled binary addons (https://nodejs.org/api/addons.html) 41 | build/Release 42 | 43 | # Dependency directories 44 | node_modules/ 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Microbundle cache 60 | .rpt2_cache/ 61 | .rts2_cache_cjs/ 62 | .rts2_cache_es/ 63 | .rts2_cache_umd/ 64 | 65 | # Optional REPL history 66 | .node_repl_history 67 | 68 | # Output of 'npm pack' 69 | *.tgz 70 | 71 | # Yarn Integrity file 72 | .yarn-integrity 73 | 74 | # dotenv environment variables file 75 | .env 76 | .env.test 77 | 78 | # parcel-bundler cache (https://parceljs.org/) 79 | .cache 80 | 81 | # Next.js build output 82 | .next 83 | 84 | # Nuxt.js build / generate output 85 | .nuxt 86 | dist 87 | 88 | # Gatsby files 89 | .cache/ 90 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 91 | # https://nextjs.org/blog/next-9-1#public-directory-support 92 | # public 93 | 94 | # vuepress build output 95 | .vuepress/dist 96 | 97 | # Serverless directories 98 | .serverless/ 99 | 100 | # FuseBox cache 101 | .fusebox/ 102 | 103 | # DynamoDB Local files 104 | .dynamodb/ 105 | 106 | # TernJS port file 107 | .tern-port 108 | 109 | # vim files 110 | *.swp 111 | 112 | # generated files 113 | src/rawData.js 114 | public/data.json 115 | build/ 116 | cloud-run/fleet-debugger 117 | appengine/fleet-debugger 118 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts/generated files 2 | build 3 | src/rawData.js 4 | .github 5 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": false, 3 | "trailingComma": "es5", 4 | "semi": true, 5 | "tabWidth": 2, 6 | "bracketSpacing": true, 7 | "arrowParens": "always", 8 | "printWidth": 120 9 | } -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | extends: "@googlemaps/semantic-release-config" 2 | -------------------------------------------------------------------------------- /DEVELOPMENT-README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | 72 | ## Other 73 | 74 | ### Code formatting `npx prettier --write .` 75 | 76 | Automatically formats all code 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fleet Debugger Tool 2 | 3 | A visualization and debugging tool for Google Maps Platform's Mobility Solutions, supporting [Scheduled tasks](https://developers.google.com/maps/documentation/mobility/fleet-engine/essentials/tasks-intro) and [On-demand trips](https://developers.google.com/maps/documentation/mobility/fleet-engine/essentials/trip-intro). 4 | 5 | ![Screenshot](docs/screenshots/vehiclereplay.gif) 6 | 7 | ## Using the Demo Site(s) 8 | 9 | The fastest way to get started is using our GitHub hosted site: \ 10 | https://googlemaps.github.io/fleet-debugger/demos/multiple-trips 11 | 12 | We also have demo data for: 13 | - [Scheduled task](https://googlemaps.github.io/fleet-debugger/demos/lmfs/) 14 | 15 | ### Loading Your Data 16 | 17 | Click on any empy Dataset buttons `Load Dataset` to get the `Fleet Engine Logs Loading` UI. 18 | 19 | ![Fleet Engine Logs Loading](docs/screenshots/Fleet_Engine_Logs_Loading.png) 20 | 21 | #### Direct Cloud Logging Connection (Recommended) 22 | 23 | 1. **Configure Parameters:** Configure the Cloud Logging query parameters directly within UI. 24 | 25 | 2. **Connect to Cloud Logging:** The Fleet Debugger can connect directly to your Google Cloud project's Cloud Logging. Click the `Sign in and Fetch Logs` button and follow the prompts to authenticate and grant access. You'll need appropriate IAM permissions (`roles/logging.viewer` which is also granted via `roles/viewer`) for the Fleet Debugger to read logs. 26 | 27 | #### Log Files in JSON Format 28 | 29 | 1. Export your Fleet Engine logs from Cloud Logging using one of the following filters (customize as needed): 30 | 31 | ```sql 32 | -- On-demand trips 33 | resource.type="fleetengine.googleapis.com/Fleet" 34 | AND (labels.vehicle_id="YOUR_VEHICLE_ID" OR 35 | labels.trip_id=~"(TRIP_ID_1|TRIP_ID_2)") 36 | AND timestamp >= "START_TIME" -- ISO 8601 format (YYYY-MM-DDTHH:MM:SS) 37 | AND timestamp <= "END_TIME" -- ISO 8601 format (YYYY-MM-DDTHH:MM:SS) 38 | AND ( 39 | logName:"logs/fleetengine.googleapis.com%2Fcreate_vehicle" OR 40 | logName:"logs/fleetengine.googleapis.com%2Fupdate_vehicle" OR 41 | logName:"logs/fleetengine.googleapis.com%2Fcreate_trip" OR 42 | logName:"logs/fleetengine.googleapis.com%2Fupdate_trip" 43 | ) 44 | ``` 45 | 46 | ```sql 47 | -- Scheduled tasks 48 | resource.type="fleetengine.googleapis.com/DeliveryFleet" 49 | AND (labels.delivery_vehicle_id="YOUR_VEHICLE_ID" OR 50 | labels.task_id=~"(TASK_ID_1|TASK_ID_2)") 51 | AND timestamp >= "START_TIME" -- ISO 8601 format (YYYY-MM-DDTHH:MM:SS) 52 | AND timestamp <= "END_TIME" -- ISO 8601 format (YYYY-MM-DDTHH:MM:SS) 53 | AND ( 54 | logName:"logs/fleetengine.googleapis.com%2Fcreate_delivery_vehicle" OR 55 | logName:"logs/fleetengine.googleapis.com%2Fupdate_delivery_vehicle" OR 56 | logName:"logs/fleetengine.googleapis.com%2Fcreate_task" OR 57 | logName:"logs/fleetengine.googleapis.com%2Fupdate_task" 58 | ) 59 | ``` 60 | 61 | 2. Download the logs in JSON format and optionally zip them 62 | 3. Import the JSON/ZIP file to Fleet Debugger, using the `Load JSON or ZIP file instead` button. 63 | 64 | > **Note**: All data processing happens client-side. Your logs remain in your browser's Local Storage and are not uploaded to Google/GitHub. 65 | 66 | ### Key Features 67 | 68 | - **Filter & inspect log messages:** Use customizable table views to easily find and analyze specific log entries. 69 | - **View planned navigation routes:** See the routes with traffic conditions as experienced by drivers (requires [Restricted Use Logs](#restricted-use-logs)). 70 | - **Replay vehicle movement:** Observe vehicle movement in real time or at an accelerated time-lapse. 71 | - **See requested vs. actual pickup and dropoff points:** (requires [Restricted Use Logs](#restricted-use-logs)). 72 | - **View status changes:** Track changes in vehicle, trip, and navigation status. 73 | - **Analyze GPS data:** Examine location, accuracy, and heading information. 74 | - **Visualize multiple trips:** View all trips for a single vehicle. 75 | - **Analyze GPS accuracy, speed, and heading:** Detailed analysis tools for these metrics ([GPS accuracy](docs/GPSAccuracy.md), [speed](docs/Speed.md), [heading](docs/Heading.md)). 76 | - **Analyze dwell times:** Measure time spent at specific locations ([dwell times](docs/DwellTimes.md)). 77 | - **Map and Timeslider Interaction:** Click directly on the map or the timeslider to select the nearest log event. 78 | - **Tracking (Chevron):** Use the tracking button to keep the map centered on the current event during replay. 79 | - **Exporting Logs:** Export loaded dataset to a local file for easy collaboration. 80 | 81 | ### Restricted Use Logs 82 | 83 | Planned navigation routes and requested Pickup/Dropoff points require enablement of [Restricted Use Logs](https://developers.google.com/maps/documentation/mobility/operations/cloud-logging/setup#enable_restricted_use_logs). 84 | 85 | ### Managing Datasets 86 | 87 | Each dataset (loaded from a file or Cloud Logging) has a dropdown menu: 88 | 89 | - **Save (Export):** Save the current dataset as a JSON file. 90 | - **Delete:** Remove the dataset from the Fleet Debugger. This clears the data from your browser's local storage. 91 | 92 | ### Restoring Demo Data 93 | 94 | To reload the original demo data: 95 | 1. Select "Delete" from `Dataset 1` dropdown menu. 96 | 2. Refresh the page. The demo data will be automatically reloaded into Dataset 1. 97 | 98 | ## Running Your Own Server 99 | 100 | ### Development Setup 101 | 102 | 1. Install dependencies: 103 | - [Node.js](https://nodejs.org/en/download) 104 | 105 | 2. Install node modules: 106 | ```bash 107 | npm install 108 | ``` 109 | 110 | ### Start development server 111 | 112 | ```bash 113 | npm start 114 | ``` 115 | 116 | ### Building and Deploying 117 | 118 | ```bash 119 | # Generate static build 120 | npm run build 121 | 122 | # Deploy to firebase 123 | npm install -g firebase-tools 124 | firebase deploy --only hosting 125 | ``` 126 | 127 | ## Privacy Policy 128 | 129 | This project is 100% client-side and does not collect or store any user data on servers. Please see our [Privacy Policy](docs/PRIVACY.md) for full details. 130 | 131 | ## Disclaimer 132 | 133 | This is not an officially supported Google product. 134 | 135 | ## Additional Resources 136 | 137 | - [Reporting Issues](docs/reporting-issues.md) 138 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Report a security issue 2 | 3 | To report a security issue, please use https://g.co/vulnz. We use 4 | https://g.co/vulnz for our intake, and do coordination and disclosure here on 5 | GitHub (including using GitHub Security Advisory). The Google Security Team will 6 | respond within 5 working days of your report on g.co/vulnz. 7 | 8 | To contact us about other bugs, please open an issue on GitHub. 9 | 10 | > **Note**: This file is synchronized from the https://github.com/googlemaps/.github repository. 11 | -------------------------------------------------------------------------------- /appengine/README.md: -------------------------------------------------------------------------------- 1 | # Fleet Debugger App Engine Example 2 | 3 | This demonstrates how fleet-debugger could be deployed via app engine. App engine 4 | service accounts will need to be configured to allow access to the datastore for 5 | logs (ie cloud logging or bigquery). 6 | 7 | The endpoints exposed are not authenticated and will need to be protected by something 8 | like Cloud Identity Aware Proxy. 9 | 10 | ## Deploying 11 | 12 | ``` 13 | ./deploy.sh 14 | ``` 15 | -------------------------------------------------------------------------------- /appengine/app.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022, Google, Inc. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | runtime: nodejs16 15 | service: fleet-debugger 16 | -------------------------------------------------------------------------------- /appengine/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm -rf ./fleet-debugger 3 | mkdir fleet-debugger 4 | (cd .. && npm run build) 5 | cp -a ../build fleet-debugger 6 | cp -a ../dune-buggy.js ./fleet-debugger/index.js 7 | cp -a ../components ./fleet-debugger/ 8 | cp package.json ./fleet-debugger 9 | cp app.yaml ./fleet-debugger 10 | cp fleet-debugger.sh ./fleet-debugger/ 11 | echo env_variables: >> ./fleet-debugger/app.yaml 12 | echo " APIKEY: \"$APIKEY\"" >> ./fleet-debugger/app.yaml 13 | (cd ./fleet-debugger && gcloud app deploy) 14 | -------------------------------------------------------------------------------- /appengine/fleet-debugger.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec node index.js serve --apikey=$APIKEY --port=$PORT --daysAgo=30 3 | -------------------------------------------------------------------------------- /appengine/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fleet-debugger", 3 | "version": "1.0.0", 4 | "description": "fleet-debugger LIVE", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/googlemaps/fleet-debugger.git" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "Apache 2.0", 13 | "bugs": { 14 | "url": "https://github.com/googlemaps/fleet-debugger/issues" 15 | }, 16 | "engines": { 17 | "node": ">= 16.0.0" 18 | }, 19 | "homepage": ".", 20 | "dependencies": { 21 | "@google-cloud/logging": "^9.6.1", 22 | "express": "^4.18.1", 23 | "google-maps-react": "^2.0.6", 24 | "googleapis": "^89.0.0", 25 | "lodash": "^4.17.21", 26 | "open": "^8.3.0", 27 | "yargs": "^17.2.1" 28 | }, 29 | "devDependencies": { 30 | "@typescript-eslint/eslint-plugin": "^5.2.0", 31 | "@typescript-eslint/parser": "^5.2.0", 32 | "eslint": "^7.32.0", 33 | "eslint-config-prettier": "^8.3.0", 34 | "eslint-plugin-jest": "^25.2.2", 35 | "eslint-plugin-prettier": "^4.0.0", 36 | "eslint-plugin-react": "^7.26.1", 37 | "typescript": "^4.4.4" 38 | }, 39 | "scripts": { 40 | "start": "sh ./fleet-debugger.sh" 41 | }, 42 | "eslintConfig": { 43 | "extends": [ 44 | "react-app", 45 | "react-app/jest" 46 | ] 47 | }, 48 | "browserslist": { 49 | "production": [ 50 | ">0.2%", 51 | "not dead", 52 | "not op_mini all" 53 | ], 54 | "development": [ 55 | "last 1 chrome version", 56 | "last 1 firefox version", 57 | "last 1 safari version" 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /cloud-run/README.md: -------------------------------------------------------------------------------- 1 | # Fleet Debugger Cloud Run Example 2 | 3 | This demonstrates how fleet-debugger could be deployed via cloud run. Cloud run 4 | service accounts will need to be configured to allow access to the datastore for 5 | logs (ie cloud logging or bigquery). 6 | 7 | The endpoints exposed are not authenticated and will need to be protected by something 8 | like Cloud Identity Aware Proxy. 9 | 10 | ## Deploying 11 | 12 | ``` 13 | ./deploy.sh 14 | ``` 15 | -------------------------------------------------------------------------------- /cloud-run/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm -rf ./fleet-debugger 3 | mkdir fleet-debugger 4 | (cd .. && npm run build) 5 | cp -a ../build fleet-debugger 6 | cp -a ../dune-buggy.js ./fleet-debugger/index.js 7 | cp -a ../components ./fleet-debugger/ 8 | cp package.json ./fleet-debugger 9 | cp fleet-debugger.sh ./fleet-debugger/ 10 | (cd ./fleet-debugger && gcloud run deploy --update-env-vars APIKEY=$APIKEY) 11 | -------------------------------------------------------------------------------- /cloud-run/fleet-debugger.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec node index.js serve --apikey=$APIKEY --port=$PORT 3 | -------------------------------------------------------------------------------- /cloud-run/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fleet-debugger", 3 | "version": "1.0.0", 4 | "description": "fleet-debugger LIVE", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/googlemaps/fleet-debugger.git" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "Apache 2.0", 13 | "bugs": { 14 | "url": "https://github.com/googlemaps/fleet-debugger/issues" 15 | }, 16 | "engines": { 17 | "node": ">= 16.0.0" 18 | }, 19 | "homepage": ".", 20 | "dependencies": { 21 | "@google-cloud/logging": "^9.6.1", 22 | "express": "^4.18.1", 23 | "google-maps-react": "^2.0.6", 24 | "googleapis": "^89.0.0", 25 | "lodash": "^4.17.21", 26 | "open": "^8.3.0", 27 | "yargs": "^17.2.1" 28 | }, 29 | "devDependencies": { 30 | "@typescript-eslint/eslint-plugin": "^5.2.0", 31 | "@typescript-eslint/parser": "^5.2.0", 32 | "eslint": "^7.32.0", 33 | "eslint-config-prettier": "^8.3.0", 34 | "eslint-plugin-jest": "^25.2.2", 35 | "eslint-plugin-prettier": "^4.0.0", 36 | "eslint-plugin-react": "^7.26.1", 37 | "typescript": "^4.4.4" 38 | }, 39 | "scripts": { 40 | "start": "sh ./fleet-debugger.sh" 41 | }, 42 | "eslintConfig": { 43 | "extends": [ 44 | "react-app", 45 | "react-app/jest" 46 | ] 47 | }, 48 | "browserslist": { 49 | "production": [ 50 | ">0.2%", 51 | "not dead", 52 | "not op_mini all" 53 | ], 54 | "development": [ 55 | "last 1 chrome version", 56 | "last 1 firefox version", 57 | "last 1 safari version" 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /components/auth.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | const { google } = require("googleapis"); 17 | const iam = google.iam("v1"); 18 | 19 | let authClient; 20 | let authedProject; 21 | 22 | /** 23 | * Initialize authorization -- relies on ADC setup by 24 | * gcloud. See README.md for details 25 | */ 26 | async function init() { 27 | await authorize(); 28 | console.log("Using project", authedProject); 29 | } 30 | 31 | /** 32 | * Create a JWT suitable for use in the JS JS SDK 33 | */ 34 | async function mintJWT() { 35 | const now = Math.round(Date.now() / 1000); 36 | 37 | //TODO: Expose service name as command line option 38 | //TODO: Ensure listed service account matches current documented 39 | // best practices on devsite. 40 | const serviceAccount = `server@${authedProject}.iam.gserviceaccount.com`; 41 | 42 | const request = { 43 | name: `projects/-/serviceAccounts/${serviceAccount}`, 44 | resource: { 45 | payload: JSON.stringify({ 46 | iss: serviceAccount, 47 | sub: serviceAccount, 48 | aud: "https://fleetengine.googleapis.com/", 49 | iat: now - 300, // don't trust clocks ... backdate a little bit 50 | exp: now + 3300, 51 | authorization: { 52 | vehicleid: "*", 53 | tripid: "*", 54 | taskid: "*", 55 | }, 56 | }), 57 | }, 58 | auth: authClient, 59 | }; 60 | try { 61 | const response = await iam.projects.serviceAccounts.signJwt(request); 62 | return response.data.signedJwt; 63 | } catch (err) { 64 | // TODO provide link to auth troubling shooting site 65 | console.error("Failed to mint JWT", err); 66 | } 67 | } 68 | 69 | /** 70 | * Verify ADC setup and cache authClient and project id for 71 | * use in other modules 72 | */ 73 | async function authorize() { 74 | const auth = new google.auth.GoogleAuth({ 75 | scopes: ["https://www.googleapis.com/auth/cloud-platform"], 76 | }); 77 | 78 | authClient = await auth.getClient(); 79 | authedProject = await auth.getProjectId(); 80 | } 81 | 82 | function getAuthClient() { 83 | return authClient; 84 | } 85 | 86 | function getProjectId() { 87 | return authedProject; 88 | } 89 | 90 | exports.init = init; 91 | exports.mintJWT = mintJWT; 92 | exports.getAuthClient = getAuthClient; 93 | exports.getProjectId = getProjectId; 94 | -------------------------------------------------------------------------------- /components/browser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | const open = require("open"); 17 | const fs = require("fs"); 18 | const http = require("http"); 19 | 20 | /* 21 | * Opens system default brower to specified path 22 | */ 23 | function openPage(filePath) { 24 | open(filePath); 25 | } 26 | 27 | /* 28 | * Starts up local express server & opens browser to 29 | * it 30 | */ 31 | function servePage(filePath) { 32 | http 33 | .createServer((req, res) => { 34 | fs.readFile(filePath, (err, data) => { 35 | if (err) { 36 | res.writeHead(404); 37 | res.end(JSON.stringify(err)); 38 | return; 39 | } 40 | res.setHeader("Content-Type", "text/html"); 41 | res.writeHead(200); 42 | res.end(data); 43 | }); 44 | }) 45 | .listen(8080); 46 | 47 | // TODO: use real paths 48 | // TODO: expose endpoints to regenerate html & reload 49 | // so that new logs can be shown 50 | open("http://localhost:8080/vehicle.html"); 51 | } 52 | exports.openPage = openPage; 53 | exports.servePage = servePage; 54 | -------------------------------------------------------------------------------- /components/cloudLoggingDS.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyvaright 2021 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | const { Datasource } = require("./datasource.js"); 17 | const logging = require("./logging.js"); 18 | const _ = require("lodash"); 19 | 20 | class CloudLogs extends Datasource { 21 | /* 22 | * Extract trip_ids from vehicle logs, and query again to 23 | * get the createTrip/update trip calls that may not be labeled 24 | * with a vehicle 25 | */ 26 | async fetchTripLogsForVehicle(vehicle_id, vehicleLogs) { 27 | if (!vehicle_id) { 28 | return []; 29 | } 30 | console.log("Loading trip logs for vehicle id", vehicle_id); 31 | const trip_ids = _(vehicleLogs) 32 | .map((x) => _.split(_.get(x, "labels.trip_id"), ",")) 33 | .flatten() 34 | .uniq() 35 | .compact() 36 | .value(); 37 | let tripLogs = []; 38 | if (trip_ids.length > 0) { 39 | console.log("trip_ids found", trip_ids); 40 | tripLogs = await logging.fetchLogs( 41 | "trip_id", 42 | trip_ids, 43 | this.argv.daysAgo, 44 | "jsonPayload.@type=type.googleapis.com/maps.fleetengine.v1.CreateTripLog" 45 | ); 46 | } else { 47 | console.log(`no trips associated with vehicle id ${this.argv.vehicle}`); 48 | } 49 | return tripLogs; 50 | } 51 | 52 | /* 53 | * Extract task_ids from vehicle logs, and query again to 54 | * get the createTask/updateTask calls that may not be labeled 55 | * with a vehicle 56 | */ 57 | async fetchTaskLogsForVehicle(vehicle_id, vehicleLogs) { 58 | if (!vehicle_id) { 59 | return []; 60 | } 61 | console.log("Loading tasks logs for deliveryVehicle id", vehicle_id); 62 | let task_ids = _(vehicleLogs) 63 | .map((logEntry) => 64 | _.get(logEntry, "jsonPayload.response.remainingVehicleJourneySegments") 65 | ) 66 | .flatten() 67 | .map((segment) => _.get(segment, "stop.tasks")) 68 | .flatten() 69 | .map((tasks) => _.get(tasks, "taskId")) 70 | .flatten() 71 | .uniq() 72 | .compact() 73 | .value(); 74 | let taskLogs = []; 75 | if (task_ids.length > 20) { 76 | // See https://github.com/googlemaps/fleet-debugger/issues/100 77 | console.warn("Too many tasks found, limiting detailed logs to first 20"); 78 | task_ids = task_ids.slice(0, 20); 79 | } 80 | if (task_ids.length > 0) { 81 | console.log("gots task_ids", task_ids); 82 | taskLogs = await logging.fetchLogs( 83 | "task_id", 84 | task_ids, 85 | this.argv.daysAgo 86 | ); 87 | } else { 88 | console.log(`no tasks associated with vehicle id ${this.argv.vehicle}`); 89 | } 90 | return taskLogs; 91 | } 92 | 93 | async fetchVehicleAndTripLogs(vehicle, trip) { 94 | const label = vehicle ? "vehicle_id" : "trip_id"; 95 | const labelVal = vehicle ? vehicle : trip; 96 | 97 | console.log(`Fetching logs for ${label} = ${labelVal}`); 98 | const logs = await logging.fetchLogs(label, [labelVal], this.argv.daysAgo); 99 | const tripLogs = await this.fetchTripLogsForVehicle(vehicle, logs); 100 | return _.concat(logs, tripLogs); 101 | } 102 | 103 | async fetchDeliveryVehicleLogs(deliveryVehicle, vehicleLogs) { 104 | if (vehicleLogs.length !== 0) { 105 | // regular vehicle logs found, not a deliveryVehicle 106 | return []; 107 | } 108 | // TODO: is it more efficient to run the log query twice 109 | // or to update the log filter with an OR? 110 | // 111 | // Could also force the user to specify which type of vehicle they're interested 112 | // in on the command line -- but that seems unfriendly & error prone 113 | console.log("fetching logs for deliveryVehicle", deliveryVehicle); 114 | return await logging.fetchLogs( 115 | "delivery_vehicle_id", 116 | [deliveryVehicle], 117 | this.argv.daysAgo 118 | ); 119 | } 120 | } 121 | 122 | exports.CloudLogs = CloudLogs; 123 | -------------------------------------------------------------------------------- /components/datasource.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyvaright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | class Datasource { 18 | constructor(argv) { 19 | this.argv = argv; 20 | } 21 | 22 | async fetchVehicleAndTripLogs(vehicle, trip) { 23 | throw new Error("Not Implemented", vehicle, trip); 24 | } 25 | 26 | async fetchDeliveryVehicleLogs(deliveryVehicle, vehicleLogs) { 27 | throw new Error("Not Implemented", deliveryVehicle, vehicleLogs); 28 | } 29 | 30 | async fetchTaskLogsForVehicle(vehicle_id, vehicleLogs) { 31 | throw new Error("Not Implemented", vehicle_id, vehicleLogs); 32 | } 33 | } 34 | 35 | exports.Datasource = Datasource; 36 | -------------------------------------------------------------------------------- /components/fleetArchiveDS.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const { Datasource } = require("./datasource.js"); 18 | const logging = require("./logging.js"); 19 | const _ = require("lodash"); 20 | 21 | class FleetArchiveLogs extends Datasource { 22 | constructor(argv) { 23 | super(argv); 24 | this.startTimeSeconds = Math.round( 25 | new Date(argv.startTime).getTime() / 1000 26 | ); 27 | this.endTimeSeconds = Math.round(new Date(argv.endTime).getTime() / 1000); 28 | } 29 | async fetchTripLogsForVehicle(vehicleId, vehicleLogs, jwt) { 30 | if (!vehicleId) { 31 | return []; 32 | } 33 | console.log("Loading trip logs for vehicle id", vehicleId); 34 | const tripIds = _(vehicleLogs) 35 | .map( 36 | (logEntry) => 37 | _.get(logEntry, "createVehicle.response.currentTrips") || 38 | _.get(logEntry, "updateVehicle.response.currentTrips") 39 | ) 40 | .flatten() 41 | .uniq() 42 | .compact() 43 | .value(); 44 | let tripLogs = []; 45 | if (tripIds.length > 0) { 46 | console.log("trip ids found", tripIds); 47 | for (const tripId of tripIds) { 48 | const logs = await logging.fetchLogsFromArchive( 49 | "trips", 50 | tripId, 51 | this.startTimeSeconds, 52 | this.endTimeSeconds, 53 | jwt 54 | ); 55 | if (logs) { 56 | tripLogs = _.concat(tripLogs, logs); 57 | } 58 | } 59 | } else { 60 | console.log(`no trips associated with vehicle id ${vehicleId}`); 61 | } 62 | return tripLogs; 63 | } 64 | 65 | async fetchTaskLogsForVehicle(vehicleId, vehicleLogs, jwt) { 66 | if (!vehicleId) { 67 | return []; 68 | } 69 | console.log("Loading tasks logs for deliveryVehicle id", vehicleId); 70 | let task_ids = _(vehicleLogs) 71 | .map( 72 | (logEntry) => 73 | _.get( 74 | logEntry, 75 | "createDeliveryVehicle.response.remainingVehicleJourneySegments" 76 | ) || 77 | _.get( 78 | logEntry, 79 | "updateDeliveryVehicle.response.remainingVehicleJourneySegments" 80 | ) 81 | ) 82 | .flatten() 83 | .map((segment) => _.get(segment, "stop.tasks")) 84 | .flatten() 85 | .map((tasks) => _.get(tasks, "taskId")) 86 | .flatten() 87 | .uniq() 88 | .compact() 89 | .value(); 90 | let taskLogs = []; 91 | if (task_ids.length > 20) { 92 | // See https://github.com/googlemaps/fleet-debugger/issues/100 93 | console.warn("Too many tasks found, limiting detailed logs to first 20"); 94 | task_ids = task_ids.slice(0, 20); 95 | } 96 | if (task_ids.length > 0) { 97 | console.log("gots task_ids", task_ids); 98 | for (const task_id of task_ids) { 99 | const logs = await logging.fetchLogsFromArchive( 100 | "tasks", 101 | task_id, 102 | this.startTimeSeconds, 103 | this.endTimeSeconds, 104 | jwt 105 | ); 106 | if (logs) { 107 | taskLogs = _.concat(taskLogs, logs); 108 | } 109 | } 110 | } else { 111 | console.log(`no tasks associated with vehicle id ${this.argv.vehicle}`); 112 | } 113 | return taskLogs; 114 | } 115 | 116 | async fetchVehicleAndTripLogs(vehicle, trip, jwt) { 117 | const label = vehicle ? "vehicles" : "trips"; 118 | const labelVal = vehicle ? vehicle : trip; 119 | 120 | console.log(`Fetching logs for ${label} = ${labelVal} from Fleet Archive`); 121 | const entries = await logging.fetchLogsFromArchive( 122 | label, 123 | labelVal, 124 | this.startTimeSeconds, 125 | this.endTimeSeconds, 126 | jwt 127 | ); 128 | 129 | if (vehicle) { 130 | const tripLogs = await this.fetchTripLogsForVehicle( 131 | vehicle, 132 | entries, 133 | jwt 134 | ); 135 | return _.concat(entries, tripLogs); 136 | } 137 | 138 | // For trip logs, need to get the logs for the vehicle and then filter on the given trip 139 | console.log(`Logs found, looking for vehicle id`); 140 | const vehicleIds = _(entries) 141 | .map( 142 | (entry) => 143 | _.get(entry, "createTrip.response.vehicleId") || 144 | _.get(entry, "updateTrip.response.vehicleId") 145 | ) 146 | .uniq() 147 | .compact() 148 | .value(); 149 | if (vehicleIds.length === 0) { 150 | return entries; 151 | } 152 | 153 | let all_filtered_vehicle_entries = []; 154 | for (const vehicleId of vehicleIds) { 155 | console.log(`Fetching logs for vehicle ${vehicleId}`); 156 | const vehicle_entries = await logging.fetchLogsFromArchive( 157 | "vehicles", 158 | vehicleId, 159 | this.startTimeSeconds, 160 | this.endTimeSeconds, 161 | jwt 162 | ); 163 | console.log(`Filtering vehicle logs by trip ${labelVal}`); 164 | const filtered_vehicle_entries = _.filter(vehicle_entries, (entry) => { 165 | const currentTrips = 166 | _.get(entry, "createVehicle.response.currentTrips") || 167 | _.get(entry, "updateVehicle.response.currentTrips"); 168 | return currentTrips && currentTrips.includes(trip); 169 | }); 170 | all_filtered_vehicle_entries = _.concat( 171 | all_filtered_vehicle_entries, 172 | filtered_vehicle_entries 173 | ); 174 | } 175 | return _.concat(entries, all_filtered_vehicle_entries); 176 | } 177 | 178 | async fetchDeliveryVehicleLogs(deliveryVehicle, vehicleLogs, jwt) { 179 | if (vehicleLogs.length !== 0) { 180 | // regular vehicle logs found, not a deliveryVehicle 181 | return []; 182 | } 183 | console.log("fetching logs for deliveryVehicle", deliveryVehicle); 184 | return await logging.fetchLogsFromArchive( 185 | "deliveryVehicles", 186 | deliveryVehicle, 187 | this.startTimeSeconds, 188 | this.endTimeSeconds, 189 | jwt 190 | ); 191 | } 192 | } 193 | 194 | exports.FleetArchiveLogs = FleetArchiveLogs; 195 | -------------------------------------------------------------------------------- /components/logging.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | //const {Logging} = require('@google-cloud/logging'); 17 | const { google } = require("googleapis"); 18 | const logging = google.logging("v2"); 19 | const auth = require("./auth.js"); 20 | const axios = require("axios"); 21 | const _ = require("lodash"); 22 | const fs = require("fs"); 23 | 24 | /* 25 | * For now, the get_trip/get_task logs are too noisy and don't have any visualization 26 | * integrations. 27 | */ 28 | const interestingLogs = [ 29 | // ODRD 30 | "create_vehicle", 31 | "update_vehicle", 32 | "get_vehicle", 33 | "create_trip", 34 | "update_trip", 35 | // LMFS 36 | "create_task", 37 | "update_task", 38 | "create_delivery_vehicle", 39 | "update_delivery_vehicle", 40 | ]; 41 | 42 | /** 43 | * Uses cloud logging APIs to list log entries from the 44 | * fleet engine resource matching the specified label. 45 | */ 46 | async function fetchLogs(label, labelValues, daysAgo = 2, extra = "") { 47 | const endPoint = "fleetengine.googleapis.com"; 48 | const odrdMonitoredResource = "fleetengine.googleapis.com/Fleet"; 49 | const lmfsMonitoredResource = "fleetengine.googleapis.com/DeliveryFleet"; 50 | const labelToMonitoredResource = { 51 | vehicle_id: odrdMonitoredResource, 52 | trip_id: odrdMonitoredResource, 53 | delivery_vehicle_id: lmfsMonitoredResource, 54 | task_id: lmfsMonitoredResource, 55 | }; 56 | const monitoredResource = labelToMonitoredResource[label]; 57 | const resourceName = "projects/" + auth.getProjectId(); 58 | // TODO better handling of date range for search: allow start/end 59 | let startDate = new Date(Date.now() - daysAgo * 24 * 3600 * 1000); 60 | const logFilterString = _.map( 61 | interestingLogs, 62 | (logName) => `${resourceName}/logs/${endPoint}%2F${logName}` 63 | ).join(" OR "); 64 | const labelFilterString = labelValues.join(" OR "); 65 | const filterString = `resource.type=${monitoredResource} labels.${label}=(${labelFilterString}) timestamp>="${startDate.toISOString()}" ${extra} log_name=(${logFilterString})`; 66 | console.log("log filter", filterString); 67 | let entries = []; 68 | try { 69 | const request = { 70 | resourceNames: [resourceName], 71 | filter: filterString, 72 | auth: auth.getAuthClient(), 73 | pageSize: 500, 74 | order_by: "timestamp desc", 75 | }; 76 | let logs; 77 | do { 78 | if (logs && logs.data.nextPageToken) { 79 | request.pageToken = logs.data.nextPageToken; 80 | } 81 | logs = await logging.entries.list(request); 82 | if (logs.data.entries) { 83 | entries = _.concat(entries, logs.data.entries); 84 | } 85 | } while (logs.data.nextPageToken); 86 | } catch (e) { 87 | console.log("failed to list logs", e); 88 | } 89 | // Remove get_trip calls -- there's too many of them 90 | return _.filter(entries, (le) => !le.logName.endsWith("get_trip")); 91 | } 92 | 93 | /** 94 | * Fetches logs from Fleet Archive using direct API calls. 95 | */ 96 | async function fetchLogsFromArchive( 97 | label, 98 | labelValue, 99 | startTimeSeconds, 100 | endTimeSeconds, 101 | jwt 102 | ) { 103 | const timeLogStr = `fetchLogsFromArchive with ${label}=${labelValue}`; 104 | console.time(timeLogStr); 105 | 106 | const odrdEndPoint = "https://fleetengine.googleapis.com/v1/archive"; 107 | const lmfsEndPoint = "https://fleetengine.googleapis.com/v1/deliveryArchive"; 108 | 109 | const labelToEndpoint = { 110 | vehicles: odrdEndPoint, 111 | trips: odrdEndPoint, 112 | deliveryVehicles: lmfsEndPoint, 113 | tasks: lmfsEndPoint, 114 | }; 115 | const endPoint = labelToEndpoint[label]; 116 | const labelToApi = { 117 | vehicles: "collectVehicleCalls", 118 | trips: "collectTripCalls", 119 | deliveryVehicles: "collectDeliveryVehicleCalls", 120 | tasks: "collectTaskCalls", 121 | }; 122 | const api = labelToApi[label]; 123 | if (!api) { 124 | console.error(`Unknown label: ${label}`); 125 | return []; 126 | } 127 | const config = { 128 | url: `${endPoint}/providers/${auth.getProjectId()}/${label}/${labelValue}:${api}`, 129 | headers: { 130 | Authorization: "Bearer " + jwt, 131 | }, 132 | params: { 133 | "time_window.start_time.seconds": startTimeSeconds, 134 | "time_window.end_time.seconds": endTimeSeconds, 135 | page_size: 50, 136 | modifying_calls_only: true, 137 | }, 138 | }; 139 | let entries = []; 140 | let pages = 0; 141 | let lastNumPagesLogged = 0; 142 | const loggingThresholdInPages = 10; 143 | try { 144 | let response; 145 | do { 146 | if (response && response.data.nextPageToken) { 147 | config.params.page_token = response.data.nextPageToken; 148 | } 149 | response = await axios(config); 150 | if (response.data.apiCalls) { 151 | entries = _.concat(entries, response.data.apiCalls); 152 | } 153 | pages++; 154 | if (pages >= lastNumPagesLogged + loggingThresholdInPages) { 155 | console.timeLog( 156 | timeLogStr, 157 | `Fetched ${pages} pages, got ${entries.length} entries` 158 | ); 159 | lastNumPagesLogged = pages; 160 | } 161 | } while (response.data.nextPageToken); 162 | } catch (err) { 163 | console.log(err); 164 | if (err.response) console.log(JSON.stringify(err.response.data)); 165 | } 166 | console.timeEnd(timeLogStr); 167 | 168 | return entries; 169 | } 170 | 171 | /** 172 | * Generates & writes a valid javascript to specified file. Existing file at location 173 | * will be overwritten. 174 | */ 175 | function writeLogs(filePath, data) { 176 | fs.writeFileSync(filePath, JSON.stringify(data)); 177 | } 178 | 179 | exports.fetchLogs = fetchLogs; 180 | exports.fetchLogsFromArchive = fetchLogsFromArchive; 181 | exports.writeLogs = writeLogs; 182 | -------------------------------------------------------------------------------- /components/serve.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Start up simple serve to handle requests for logs 3 | */ 4 | function Serve(port, getLogs, dataSource, params) { 5 | const express = require("express"); 6 | const app = express(); 7 | 8 | // Server up static debugger page 9 | app.use("/debugger", express.static("build")); 10 | 11 | app.get("/", (req, res) => { 12 | res.redirect("/debugger/?serve=true"); 13 | }); 14 | 15 | app.get("/vehicles/*", async (req, res) => { 16 | const vehicle = req.path.slice(10); 17 | console.log("Got vehicle request", vehicle); 18 | 19 | await getLogs(dataSource, params, vehicle, undefined); 20 | res.json(params); 21 | }); 22 | 23 | app.get("/trips/*", async (req, res) => { 24 | const trip = req.path.slice(7); 25 | console.log("Got trip request", trip); 26 | await getLogs(dataSource, params, undefined, trip); 27 | res.json(params); 28 | }); 29 | 30 | app.listen(port, () => { 31 | console.log(`fleet-debugger: listening on port ${port}`); 32 | }); 33 | } 34 | 35 | exports.Serve = Serve; 36 | -------------------------------------------------------------------------------- /datasets/fleet_archive_delivery.json: -------------------------------------------------------------------------------- 1 | {"vehicle":"sample-vehicle-id","projectId":"sample-project-id","logSource":"fleetarchive","solutionType":"LMFS","rawLogs":[{"serverTime":"2021-12-14T13:56:22.774107Z","getDeliveryVehicle":{"request":{"header":{},"projectNumber":"183720254657","name":"providers/sample-project-id/deliveryVehicles/sample-vehicle-id"},"response":{"name":"providers/sample-project-id/deliveryVehicles/sample-vehicle-id","lastLocation":{"location":{"latitude":40.776548,"longitude":-73.948372},"locationSensor":"GPS","serverTime":"2021-12-14T13:56:11.937024Z","rawLocation":{"latitude":40.77655,"longitude":-73.94837}},"currentRouteSegmentEndPoint":{},"remainingVehicleJourneySegments":[{"stop":{"plannedLocation":{"point":{"latitude":40.740757,"longitude":-74.001799}},"tasks":[{"taskId":"sample-vehicle-id-2","taskDuration":"10s"}],"state":"NEW"},"drivingDuration":"1851.311279s"}]}}},{"serverTime":"2021-12-14T13:56:17.831011Z","getDeliveryVehicle":{"request":{"header":{},"projectNumber":"183720254657","name":"providers/sample-project-id/deliveryVehicles/sample-vehicle-id"},"response":{"name":"providers/sample-project-id/deliveryVehicles/sample-vehicle-id","lastLocation":{"location":{"latitude":40.776548,"longitude":-73.948372},"locationSensor":"GPS","serverTime":"2021-12-14T13:56:11.937024Z","rawLocation":{"latitude":40.77655,"longitude":-73.94837}},"currentRouteSegmentEndPoint":{},"remainingVehicleJourneySegments":[{"stop":{"plannedLocation":{"point":{"latitude":40.740757,"longitude":-74.001799}},"tasks":[{"taskId":"sample-vehicle-id-2","taskDuration":"10s"}],"state":"NEW"},"drivingDuration":"1851.311279s"}]}}},{"serverTime":"2021-12-14T13:56:13.657767Z","updateTask":{"request":{"header":{},"projectNumber":"183720254657","task":{"name":"providers/sample-project-id/tasks/sample-vehicle-id-1","taskOutcome":"SUCCEEDED"},"updateMask":"taskOutcome"},"response":{"name":"providers/sample-project-id/tasks/sample-vehicle-id-1","type":"PICKUP","state":"CLOSED","trackingId":"sample-vehicle-id-1-tracking","deliveryVehicleId":"sample-vehicle-id","plannedLocation":{"point":{"latitude":40.776548,"longitude":-73.948372}},"taskDuration":"10s","taskOutcome":"SUCCEEDED","taskOutcomeLocation":{"point":{"latitude":40.77655,"longitude":-73.94837}},"taskOutcomeLocationSource":"LAST_VEHICLE_LOCATION"}}},{"serverTime":"2021-12-14T13:56:13.560431Z","getDeliveryVehicle":{"request":{"header":{},"projectNumber":"183720254657","name":"providers/sample-project-id/deliveryVehicles/sample-vehicle-id"},"response":{"name":"providers/sample-project-id/deliveryVehicles/sample-vehicle-id","lastLocation":{"location":{"latitude":40.776548,"longitude":-73.948372},"locationSensor":"GPS","serverTime":"2021-12-14T13:56:11.937024Z","rawLocation":{"latitude":40.77655,"longitude":-73.94837}},"currentRouteSegmentEndPoint":{},"remainingVehicleJourneySegments":[{"stop":{"plannedLocation":{"point":{"latitude":40.740757,"longitude":-74.001799}},"tasks":[{"taskId":"sample-vehicle-id-2","taskDuration":"10s"}],"state":"NEW"},"drivingDuration":"1851.311279s"}]}}},{"serverTime":"2021-12-14T13:56:12.958855Z","updateDeliveryVehicle":{"request":{"header":{},"projectNumber":"183720254657","deliveryVehicle":{"name":"providers/sample-project-id/deliveryVehicles/sample-vehicle-id","lastLocation":{"location":{"latitude":40.776548,"longitude":-73.948372},"serverTime":"2021-12-14T13:56:10.714160Z"},"currentRouteSegmentEndPoint":{},"remainingVehicleJourneySegments":[{"stop":{"plannedLocation":{"point":{"latitude":40.740757,"longitude":-74.001799}},"tasks":[{"taskId":"sample-vehicle-id-2","taskDuration":"10s"}],"state":"NEW"},"drivingDuration":"1875s"}]},"updateMask":"remainingVehicleJourneySegments"},"response":{"name":"providers/sample-project-id/deliveryVehicles/sample-vehicle-id","lastLocation":{"location":{"latitude":40.776548,"longitude":-73.948372},"locationSensor":"GPS","serverTime":"2021-12-14T13:56:11.937024Z","rawLocation":{"latitude":40.77655,"longitude":-73.94837}},"currentRouteSegmentEndPoint":{},"remainingVehicleJourneySegments":[{"stop":{"plannedLocation":{"point":{"latitude":40.740757,"longitude":-74.001799}},"tasks":[{"taskId":"sample-vehicle-id-2","taskDuration":"10s"}],"state":"NEW"},"drivingDuration":"1851.311279s"}]}}},{"serverTime":"2021-12-14T13:56:12.495508Z","updateDeliveryVehicle":{"request":{"header":{},"projectNumber":"183720254657","deliveryVehicle":{"name":"providers/sample-project-id/deliveryVehicles/sample-vehicle-id","lastLocation":{"location":{"latitude":40.776548,"longitude":-73.948372},"rawLocation":{"latitude":40.77655,"longitude":-73.94837}},"currentRouteSegmentEndPoint":{}},"updateMask":"lastLocation"},"response":{"name":"providers/sample-project-id/deliveryVehicles/sample-vehicle-id","lastLocation":{"location":{"latitude":40.776548,"longitude":-73.948372},"locationSensor":"GPS","serverTime":"2021-12-14T13:56:11.937024Z","rawLocation":{"latitude":40.77655,"longitude":-73.94837}},"currentRouteSegmentEndPoint":{},"remainingVehicleJourneySegments":[{"stop":{"plannedLocation":{"point":{"latitude":40.776548,"longitude":-73.948372}},"tasks":[{"taskId":"sample-vehicle-id-1","taskDuration":"10s"}],"state":"ENROUTE"},"drivingDuration":"0.964524s"},{"stop":{"plannedLocation":{"point":{"latitude":40.740757,"longitude":-74.001799}},"tasks":[{"taskId":"sample-vehicle-id-2","taskDuration":"10s"}],"state":"NEW"},"drivingDuration":"1885s"}]}}},{"serverTime":"2021-12-14T13:56:11.696660Z","updateDeliveryVehicle":{"request":{"header":{},"projectNumber":"183720254657","deliveryVehicle":{"name":"providers/sample-project-id/deliveryVehicles/sample-vehicle-id","currentRouteSegmentEndPoint":{},"remainingVehicleJourneySegments":[{"stop":{"plannedLocation":{"point":{"latitude":40.776548,"longitude":-73.948372}},"tasks":[{"taskId":"sample-vehicle-id-1","taskDuration":"10s"}],"state":"ENROUTE"}},{"stop":{"plannedLocation":{"point":{"latitude":40.740757,"longitude":-74.001799}},"tasks":[{"taskId":"sample-vehicle-id-2","taskDuration":"10s"}],"state":"NEW"}}]},"updateMask":"remainingVehicleJourneySegments"},"response":{"name":"providers/sample-project-id/deliveryVehicles/sample-vehicle-id","lastLocation":{"location":{"latitude":40.776548,"longitude":-73.948372},"serverTime":"2021-12-14T13:56:10.714160Z"},"currentRouteSegmentEndPoint":{},"remainingVehicleJourneySegments":[{"stop":{"plannedLocation":{"point":{"latitude":40.776548,"longitude":-73.948372}},"tasks":[{"taskId":"sample-vehicle-id-1","taskDuration":"10s"}],"state":"ENROUTE"},"drivingDuration":"0.379216s"},{"stop":{"plannedLocation":{"point":{"latitude":40.740757,"longitude":-74.001799}},"tasks":[{"taskId":"sample-vehicle-id-2","taskDuration":"10s"}],"state":"NEW"},"drivingDuration":"1875s"}]}}},{"serverTime":"2021-12-14T13:56:10.879281Z","createDeliveryVehicle":{"request":{"header":{},"projectNumber":"183720254657","parent":"providers/sample-project-id","deliveryVehicleId":"sample-vehicle-id","deliveryVehicle":{"lastLocation":{"location":{"latitude":40.776548,"longitude":-73.948372},"rawLocation":{"latitude":40.77655,"longitude":-73.94837}},"currentRouteSegmentEndPoint":{}}},"response":{"name":"providers/sample-project-id/deliveryVehicles/sample-vehicle-id","lastLocation":{"location":{"latitude":40.776548,"longitude":-73.948372},"serverTime":"2021-12-14T13:56:10.714160Z"},"currentRouteSegmentEndPoint":{}}}},{"serverTime":"2021-12-14T13:56:10.552624Z","createTask":{"request":{"header":{},"projectNumber":"183720254657","parent":"providers/sample-project-id","task":{"type":"DELIVERY","state":"OPEN","trackingId":"sample-vehicle-id-2-tracking","plannedLocation":{"point":{"latitude":40.740757,"longitude":-74.001799}},"taskDuration":"10s"},"taskId":"sample-vehicle-id-2"},"response":{"name":"providers/sample-project-id/tasks/sample-vehicle-id-2","type":"DELIVERY","state":"OPEN","trackingId":"sample-vehicle-id-2-tracking","plannedLocation":{"point":{"latitude":40.740757,"longitude":-74.001799}},"taskDuration":"10s"}}},{"serverTime":"2021-12-14T13:56:10.213837Z","createTask":{"request":{"header":{},"projectNumber":"183720254657","parent":"providers/sample-project-id","task":{"type":"PICKUP","state":"OPEN","trackingId":"sample-vehicle-id-1-tracking","plannedLocation":{"point":{"latitude":40.776548,"longitude":-73.948372}},"taskDuration":"10s"},"taskId":"sample-vehicle-id-1"},"response":{"name":"providers/sample-project-id/tasks/sample-vehicle-id-1","type":"PICKUP","state":"OPEN","trackingId":"sample-vehicle-id-1-tracking","plannedLocation":{"point":{"latitude":40.776548,"longitude":-73.948372}},"taskDuration":"10s"}}}]} -------------------------------------------------------------------------------- /demos/README.md: -------------------------------------------------------------------------------- 1 | # Demo Datasets 2 | 3 | Sample output 4 | 5 | ## Setup 6 | 7 | Install the 'serve' tool 8 | 9 | ``` 10 | npm install -g serve 11 | ``` 12 | 13 | 14 | ## Running 15 | 16 | ``` 17 | serve -s demos/jump 18 | ``` 19 | 20 | ## Viewing 21 | Open the following link in your browser to view the demo: 22 | 23 | ``` 24 | http://localhost:3000?apikey=your-api-key 25 | ``` 26 | 27 | By default serve will use port 3000 (as above). If the port printed out by the ```serve -s``` commind is different use that port number instead. 28 | -------------------------------------------------------------------------------- /demos/jump/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "./static/css/main.fabf1335.css", 4 | "main.js": "./static/js/main.03d2a252.js", 5 | "index.html": "./index.html", 6 | "main.fabf1335.css.map": "./static/css/main.fabf1335.css.map", 7 | "main.03d2a252.js.map": "./static/js/main.03d2a252.js.map" 8 | }, 9 | "entrypoints": [ 10 | "static/css/main.fabf1335.css", 11 | "static/js/main.03d2a252.js" 12 | ] 13 | } -------------------------------------------------------------------------------- /demos/jump/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/demos/jump/favicon.ico -------------------------------------------------------------------------------- /demos/jump/index.html: -------------------------------------------------------------------------------- 1 | Fleet Debugger
-------------------------------------------------------------------------------- /demos/jump/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/demos/jump/logo192.png -------------------------------------------------------------------------------- /demos/jump/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/demos/jump/logo512.png -------------------------------------------------------------------------------- /demos/jump/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Fleet Debugger", 3 | "name": "Fleet Debugger'", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /demos/jump/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /demos/jump/static/js/main.03d2a252.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | Copyright (c) 2018 Jed Watson. 9 | Licensed under the MIT License (MIT), see 10 | http://jedwatson.github.io/classnames 11 | */ 12 | 13 | /*! 14 | 15 | JSZip v3.10.1 - A JavaScript class for generating and reading zip files 16 | 17 | 18 | (c) 2009-2016 Stuart Knightley 19 | Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown. 20 | 21 | JSZip uses the library pako released under the MIT license : 22 | https://github.com/nodeca/pako/blob/main/LICENSE 23 | */ 24 | 25 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ 26 | 27 | /** 28 | * @license 29 | * Lodash 30 | * Copyright OpenJS Foundation and other contributors 31 | * Released under MIT license 32 | * Based on Underscore.js 1.8.3 33 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 34 | */ 35 | 36 | /** 37 | * @license React 38 | * react-is.production.min.js 39 | * 40 | * Copyright (c) Facebook, Inc. and its affiliates. 41 | * 42 | * This source code is licensed under the MIT license found in the 43 | * LICENSE file in the root directory of this source tree. 44 | */ 45 | 46 | /** @license React v0.20.2 47 | * scheduler.production.min.js 48 | * 49 | * Copyright (c) Facebook, Inc. and its affiliates. 50 | * 51 | * This source code is licensed under the MIT license found in the 52 | * LICENSE file in the root directory of this source tree. 53 | */ 54 | 55 | /** @license React v17.0.2 56 | * react-dom.production.min.js 57 | * 58 | * Copyright (c) Facebook, Inc. and its affiliates. 59 | * 60 | * This source code is licensed under the MIT license found in the 61 | * LICENSE file in the root directory of this source tree. 62 | */ 63 | 64 | /** @license React v17.0.2 65 | * react-jsx-runtime.production.min.js 66 | * 67 | * Copyright (c) Facebook, Inc. and its affiliates. 68 | * 69 | * This source code is licensed under the MIT license found in the 70 | * LICENSE file in the root directory of this source tree. 71 | */ 72 | 73 | /** @license React v17.0.2 74 | * react.production.min.js 75 | * 76 | * Copyright (c) Facebook, Inc. and its affiliates. 77 | * 78 | * This source code is licensed under the MIT license found in the 79 | * LICENSE file in the root directory of this source tree. 80 | */ 81 | -------------------------------------------------------------------------------- /demos/jump/view-bundle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Simple script to view a support bundle. 4 | # Relies on https://www.npmjs.com/package/serve 5 | # 6 | if [[ -z "${APIKEY}" ]]; then 7 | echo "APIKEY environment variable must exist" 8 | fi 9 | 10 | if ! [ -x "$(command -v serve)" ]; then 11 | echo 'Error: serve is not installed. Install with "npm install -g serve"' >&2 12 | exit 1 13 | fi 14 | 15 | url="http://localhost:3000?apikey=$APIKEY" 16 | echo "Attempting to open browser to $url" 17 | open $url 18 | serve -s 19 | -------------------------------------------------------------------------------- /demos/lmfs/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "./static/css/main.fabf1335.css", 4 | "main.js": "./static/js/main.03d2a252.js", 5 | "index.html": "./index.html", 6 | "main.fabf1335.css.map": "./static/css/main.fabf1335.css.map", 7 | "main.03d2a252.js.map": "./static/js/main.03d2a252.js.map" 8 | }, 9 | "entrypoints": [ 10 | "static/css/main.fabf1335.css", 11 | "static/js/main.03d2a252.js" 12 | ] 13 | } -------------------------------------------------------------------------------- /demos/lmfs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/demos/lmfs/favicon.ico -------------------------------------------------------------------------------- /demos/lmfs/index.html: -------------------------------------------------------------------------------- 1 | Fleet Debugger
-------------------------------------------------------------------------------- /demos/lmfs/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/demos/lmfs/logo192.png -------------------------------------------------------------------------------- /demos/lmfs/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/demos/lmfs/logo512.png -------------------------------------------------------------------------------- /demos/lmfs/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Fleet Debugger", 3 | "name": "Fleet Debugger'", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /demos/lmfs/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /demos/lmfs/static/js/main.03d2a252.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | Copyright (c) 2018 Jed Watson. 9 | Licensed under the MIT License (MIT), see 10 | http://jedwatson.github.io/classnames 11 | */ 12 | 13 | /*! 14 | 15 | JSZip v3.10.1 - A JavaScript class for generating and reading zip files 16 | 17 | 18 | (c) 2009-2016 Stuart Knightley 19 | Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown. 20 | 21 | JSZip uses the library pako released under the MIT license : 22 | https://github.com/nodeca/pako/blob/main/LICENSE 23 | */ 24 | 25 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ 26 | 27 | /** 28 | * @license 29 | * Lodash 30 | * Copyright OpenJS Foundation and other contributors 31 | * Released under MIT license 32 | * Based on Underscore.js 1.8.3 33 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 34 | */ 35 | 36 | /** 37 | * @license React 38 | * react-is.production.min.js 39 | * 40 | * Copyright (c) Facebook, Inc. and its affiliates. 41 | * 42 | * This source code is licensed under the MIT license found in the 43 | * LICENSE file in the root directory of this source tree. 44 | */ 45 | 46 | /** @license React v0.20.2 47 | * scheduler.production.min.js 48 | * 49 | * Copyright (c) Facebook, Inc. and its affiliates. 50 | * 51 | * This source code is licensed under the MIT license found in the 52 | * LICENSE file in the root directory of this source tree. 53 | */ 54 | 55 | /** @license React v17.0.2 56 | * react-dom.production.min.js 57 | * 58 | * Copyright (c) Facebook, Inc. and its affiliates. 59 | * 60 | * This source code is licensed under the MIT license found in the 61 | * LICENSE file in the root directory of this source tree. 62 | */ 63 | 64 | /** @license React v17.0.2 65 | * react-jsx-runtime.production.min.js 66 | * 67 | * Copyright (c) Facebook, Inc. and its affiliates. 68 | * 69 | * This source code is licensed under the MIT license found in the 70 | * LICENSE file in the root directory of this source tree. 71 | */ 72 | 73 | /** @license React v17.0.2 74 | * react.production.min.js 75 | * 76 | * Copyright (c) Facebook, Inc. and its affiliates. 77 | * 78 | * This source code is licensed under the MIT license found in the 79 | * LICENSE file in the root directory of this source tree. 80 | */ 81 | -------------------------------------------------------------------------------- /demos/lmfs/view-bundle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Simple script to view a support bundle. 4 | # Relies on https://www.npmjs.com/package/serve 5 | # 6 | if [[ -z "${APIKEY}" ]]; then 7 | echo "APIKEY environment variable must exist" 8 | fi 9 | 10 | if ! [ -x "$(command -v serve)" ]; then 11 | echo 'Error: serve is not installed. Install with "npm install -g serve"' >&2 12 | exit 1 13 | fi 14 | 15 | url="http://localhost:3000?apikey=$APIKEY" 16 | echo "Attempting to open browser to $url" 17 | open $url 18 | serve -s 19 | -------------------------------------------------------------------------------- /demos/multiple-trips/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "./static/css/main.fabf1335.css", 4 | "main.js": "./static/js/main.03d2a252.js", 5 | "index.html": "./index.html", 6 | "main.fabf1335.css.map": "./static/css/main.fabf1335.css.map", 7 | "main.03d2a252.js.map": "./static/js/main.03d2a252.js.map" 8 | }, 9 | "entrypoints": [ 10 | "static/css/main.fabf1335.css", 11 | "static/js/main.03d2a252.js" 12 | ] 13 | } -------------------------------------------------------------------------------- /demos/multiple-trips/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/demos/multiple-trips/favicon.ico -------------------------------------------------------------------------------- /demos/multiple-trips/index.html: -------------------------------------------------------------------------------- 1 | Fleet Debugger
-------------------------------------------------------------------------------- /demos/multiple-trips/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/demos/multiple-trips/logo192.png -------------------------------------------------------------------------------- /demos/multiple-trips/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/demos/multiple-trips/logo512.png -------------------------------------------------------------------------------- /demos/multiple-trips/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Fleet Debugger", 3 | "name": "Fleet Debugger'", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /demos/multiple-trips/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /demos/multiple-trips/static/js/main.03d2a252.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | Copyright (c) 2018 Jed Watson. 9 | Licensed under the MIT License (MIT), see 10 | http://jedwatson.github.io/classnames 11 | */ 12 | 13 | /*! 14 | 15 | JSZip v3.10.1 - A JavaScript class for generating and reading zip files 16 | 17 | 18 | (c) 2009-2016 Stuart Knightley 19 | Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown. 20 | 21 | JSZip uses the library pako released under the MIT license : 22 | https://github.com/nodeca/pako/blob/main/LICENSE 23 | */ 24 | 25 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ 26 | 27 | /** 28 | * @license 29 | * Lodash 30 | * Copyright OpenJS Foundation and other contributors 31 | * Released under MIT license 32 | * Based on Underscore.js 1.8.3 33 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 34 | */ 35 | 36 | /** 37 | * @license React 38 | * react-is.production.min.js 39 | * 40 | * Copyright (c) Facebook, Inc. and its affiliates. 41 | * 42 | * This source code is licensed under the MIT license found in the 43 | * LICENSE file in the root directory of this source tree. 44 | */ 45 | 46 | /** @license React v0.20.2 47 | * scheduler.production.min.js 48 | * 49 | * Copyright (c) Facebook, Inc. and its affiliates. 50 | * 51 | * This source code is licensed under the MIT license found in the 52 | * LICENSE file in the root directory of this source tree. 53 | */ 54 | 55 | /** @license React v17.0.2 56 | * react-dom.production.min.js 57 | * 58 | * Copyright (c) Facebook, Inc. and its affiliates. 59 | * 60 | * This source code is licensed under the MIT license found in the 61 | * LICENSE file in the root directory of this source tree. 62 | */ 63 | 64 | /** @license React v17.0.2 65 | * react-jsx-runtime.production.min.js 66 | * 67 | * Copyright (c) Facebook, Inc. and its affiliates. 68 | * 69 | * This source code is licensed under the MIT license found in the 70 | * LICENSE file in the root directory of this source tree. 71 | */ 72 | 73 | /** @license React v17.0.2 74 | * react.production.min.js 75 | * 76 | * Copyright (c) Facebook, Inc. and its affiliates. 77 | * 78 | * This source code is licensed under the MIT license found in the 79 | * LICENSE file in the root directory of this source tree. 80 | */ 81 | -------------------------------------------------------------------------------- /demos/multiple-trips/view-bundle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Simple script to view a support bundle. 4 | # Relies on https://www.npmjs.com/package/serve 5 | # 6 | if [[ -z "${APIKEY}" ]]; then 7 | echo "APIKEY environment variable must exist" 8 | fi 9 | 10 | if ! [ -x "$(command -v serve)" ]; then 11 | echo 'Error: serve is not installed. Install with "npm install -g serve"' >&2 12 | exit 1 13 | fi 14 | 15 | url="http://localhost:3000?apikey=$APIKEY" 16 | echo "Attempting to open browser to $url" 17 | open $url 18 | serve -s 19 | -------------------------------------------------------------------------------- /demos/rebuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Run this script from the root directory to rebuild all 4 | # the demos to pickup any new visualizations 5 | 6 | rm -rf build 7 | rm -rf demos/jump 8 | cp datasets/jump-demo.json public/data.json 9 | npm run build 10 | cp -a build demos/jump 11 | 12 | rm -rf build 13 | rm -rf demos/lmfs 14 | cp datasets/lmfs.json public/data.json 15 | npm run build 16 | cp -a build demos/lmfs 17 | 18 | rm -rf build 19 | rm -rf demos/multiple-trips 20 | cp datasets/two-trips-bay-area.json public/data.json 21 | npm run build 22 | cp -a build demos/multiple-trips 23 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement (CLA). You (or your employer) retain the copyright to your 10 | contribution; this simply gives us permission to use and redistribute your 11 | contributions as part of the project. Head over to 12 | to see your current agreements on file or 13 | to sign a new one. 14 | 15 | You generally only need to submit a CLA once, so if you've already submitted one 16 | (even if it was for a different project), you probably don't need to do it 17 | again. 18 | 19 | ## Code Reviews 20 | 21 | All submissions, including submissions by project members, require review. We 22 | use GitHub pull requests for this purpose. Consult 23 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 24 | information on using pull requests. 25 | 26 | ## Community Guidelines 27 | 28 | This project follows 29 | [Google's Open Source Community Guidelines](https://opensource.google/conduct/). 30 | -------------------------------------------------------------------------------- /docs/DwellTimes.md: -------------------------------------------------------------------------------- 1 | # Dwell Time 2 | 3 | The "Dwell Locations" toggle highlights areas on the map where the vehicle remained stationary for a significant period. 4 | 5 | * **What they represent:** These are locations where the vehicle stayed within a **20-meter radius** for at least **2 minutes**. 6 | * **How they appear:** Dwell locations are shown as yellow circles. 7 | * **Circle size:** The *size* of the circle indicates the *number of updates* received while the vehicle was at that location. Larger circles mean more updates were received, suggesting a longer dwell time, but the size *doesn't* directly represent the duration in minutes or hours. 8 | 9 | ![Screenshot](screenshots/dwelltime.png) 10 | -------------------------------------------------------------------------------- /docs/GPSAccuracy.md: -------------------------------------------------------------------------------- 1 | # GPS Accuracy 2 | 3 | ![Screenshot](screenshots/gps_accuracy.png) 4 | -------------------------------------------------------------------------------- /docs/Heading.md: -------------------------------------------------------------------------------- 1 | # Heading 2 | 3 | ![Screenshot](screenshots/heading.png) 4 | -------------------------------------------------------------------------------- /docs/MissingUpdates.md: -------------------------------------------------------------------------------- 1 | # Missing Updates 2 | 3 | This visualiziton marks areas where no updates to the vehicle location were received for a long period of time. This period is the lower of 60 seconds or 10x the median gap between updates. 4 | -------------------------------------------------------------------------------- /docs/PRIVACY.md: -------------------------------------------------------------------------------- 1 | # Fleet Debugger Tool Privacy Policy 2 | 3 | **Last Updated:** March 15, 2025 4 | 5 | This Privacy Policy describes how the Fleet Debugger Tool (the "Tool") handles your information when you use it to access Google Cloud Logging data. The Tool is a *completely static* web application; it does *not* collect, store, or transmit any personal data or Google Cloud Logging data to any servers. 6 | 7 | ## Information Access and Usage 8 | 9 | The Tool is designed to help you visualize and debug your Google Maps Platform Mobility Solutions data stored in Google Cloud Logging. To function, it requires access to your Google Cloud project's Cloud Logging data through the following sensitive OAuth scope: 10 | 11 | * `https://www.googleapis.com/auth/logging.read`: This scope allows the Tool to read your Cloud Logging entries *within your browser*. 12 | * `https://www.googleapis.com/auth/cloud-platform.read-only`: This scope is used *solely* to enable access to Google Cloud Logging. While this is a broad scope, the Tool uses it in a *strictly limited* way. It *only* uses this scope to retrieve and process log entries related to Fleet Engine and does *not* access any other resources or data within your Google Cloud project. 13 | 14 | **Crucially:** 15 | 16 | 1. **No Server-Side Storage:** The Tool operates entirely within your web browser. No data, including your Google Cloud Logging data, your Google credentials, or any other information you provide, is sent to, stored on, or processed by any external server controlled by the Tool's developers. 17 | 2. **Client-Side Processing:** All data processing, filtering, visualization, and analysis happens *exclusively* within your browser's memory and local storage. 18 | 3. **Temporary Data Storage (Browser Local Storage):** The Tool *may* temporarily store data in your browser's Local Storage to improve performance and allow you to work with previously loaded datasets. This data is *only* accessible to the Tool running in your browser and is *not* transmitted to any external server. You can clear this data at any time by using the "Delete" option for datasets within the Tool or by clearing your browser's Local Storage. 19 | 4. **Data Export:** The tool *may* allow you to export the processed data and its representation. This operation is triggered by the user, and the data remains 100% client side. 20 | 5. **OAuth Authorization:** When you connect the Tool to your Google Cloud project, you will be redirected to Google's standard OAuth consent screen. This screen will clearly explain the permissions the Tool is requesting. You can revoke these permissions at any time through your Google Account settings ([https://myaccount.google.com/permissions](https://myaccount.google.com/permissions)). The Tool itself does *not* store your OAuth tokens; they are managed by your browser. 21 | 6. **No Sharing with Third Parties:** The Tool does *not* share your data with any third parties. 22 | 23 | ## Your Choices and Control 24 | 25 | * **Granting and Revoking Access:** You have full control over granting and revoking the Tool's access to your Google Cloud Logging data via Google's OAuth system. 26 | * **Deleting Data:** You can delete any data stored locally in your browser by using the dataset "Delete" feature within the Tool or by clearing your browser's Local Storage. 27 | * **Not Using the Tool:** If you do not agree with this Privacy Policy, you should not use the Tool. 28 | 29 | ## Data Security 30 | 31 | While the Tool does not store or transmit your data, the security of your data ultimately depends on the security of your Google Cloud project and your own device. We recommend following Google Cloud's security best practices, including: 32 | 33 | * Using strong passwords and enabling two-factor authentication for your Google Account. 34 | * Following the principle of least privilege when configuring IAM permissions for your Google Cloud project. Ensure the service account or user account you use to access Cloud Logging only has the necessary `roles/logging.viewer` role (or a custom role with equivalent limited permissions). Do *not* grant broader permissions than necessary. 35 | * Keeping your browser and operating system up to date. 36 | 37 | ## Changes to this Privacy Policy 38 | 39 | We may update this Privacy Policy from time to time. We will post any changes on this page and update the "Last Updated" date. Your continued use of the Tool after any changes constitutes your acceptance of the revised Privacy Policy. 40 | 41 | ## Contact Us 42 | 43 | If you have any questions about this Privacy Policy, please contact us [via GitHub issues](https://github.com/googlemaps/fleet-debugger/issues/new/choose). -------------------------------------------------------------------------------- /docs/PlannedPaths.md: -------------------------------------------------------------------------------- 1 | # Planned Paths 2 | 3 | Displays all planned routes for the vehicle. 4 | This data comes directly from Navigation SDK, but requires the enablement of restriced use logs. 5 | 6 | We are also working on how we can integrate planned paths that are from server side routing. e.g routes from before navigation SDK guidance started. 7 | 8 | ![Screenshot](screenshots/planned_paths.png) 9 | -------------------------------------------------------------------------------- /docs/ReplaceVehicleMovement.md: -------------------------------------------------------------------------------- 1 | # Replay Vehicle Movement 2 | 3 | Setting a speed of an update every 5 seconds would represent real time. 4 | 5 | ![Screenshot](screenshots/vehiclereplay.gif) 6 | -------------------------------------------------------------------------------- /docs/Speed.md: -------------------------------------------------------------------------------- 1 | # Speed 2 | 3 | ![Screenshot](screenshots/speed.png) 4 | -------------------------------------------------------------------------------- /docs/Tasks.md: -------------------------------------------------------------------------------- 1 | # LMFS Tasks 2 | 3 | Displays all tasks assigned to the vehicle. Time slider allows replaying task state/outcome 4 | changes. 5 | 6 | The red or green arrows indicate the delta between the planned location and outcome location 7 | for the task. 8 | 9 | Task markers are clickable and will show basic data in the json viewer. 10 | 11 | ![Screenshot](screenshots/tasks.png) 12 | -------------------------------------------------------------------------------- /docs/VelocityJumps.md: -------------------------------------------------------------------------------- 1 | # High Velocity Jumps 2 | 3 | Marks locations on the map where the vehicle traveled at an unrealistic speed. Unrealistic 4 | speed is either 240KPH/150 MPH (or 10x the median velocity). 5 | 6 | Clicking on the arrow line will limit the log viewer to the time around 7 | the jump. 8 | -------------------------------------------------------------------------------- /docs/code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of 9 | experience, education, socio-economic status, nationality, personal appearance, 10 | race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or reject 41 | comments, commits, code, wiki edits, issues, and other contributions that are 42 | not aligned to this Code of Conduct, or to ban temporarily or permanently any 43 | contributor for other behaviors that they deem inappropriate, threatening, 44 | offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | This Code of Conduct also applies outside the project spaces when the Project 56 | Steward has a reasonable belief that an individual's behavior may have a 57 | negative impact on the project or its community. 58 | 59 | ## Conflict Resolution 60 | 61 | We do not believe that all conflict is bad; healthy debate and disagreement 62 | often yield positive results. However, it is never okay to be disrespectful or 63 | to engage in behavior that violates the project’s code of conduct. 64 | 65 | If you see someone violating the code of conduct, you are encouraged to address 66 | the behavior directly with those involved. Many issues can be resolved quickly 67 | and easily, and this gives people more control over the outcome of their 68 | dispute. If you are unable to resolve the matter for any reason, or if the 69 | behavior is threatening or harassing, report it. We are dedicated to providing 70 | an environment where participants feel welcome and safe. 71 | 72 | Reports should be directed to _[PROJECT STEWARD NAME(s) AND EMAIL(s)]_, the 73 | Project Steward(s) for _[PROJECT NAME]_. It is the Project Steward’s duty to 74 | receive and address reported violations of the code of conduct. They will then 75 | work with a committee consisting of representatives from the Open Source 76 | Programs Office and the Google Open Source Strategy team. If for any reason you 77 | are uncomfortable reaching out to the Project Steward, please email 78 | opensource@google.com. 79 | 80 | We will investigate every complaint, but you may not receive a direct response. 81 | We will use our discretion in determining when and how to follow up on reported 82 | incidents, which may range from not taking action to permanent expulsion from 83 | the project and project-sponsored spaces. We will notify the accused of the 84 | report and provide them an opportunity to discuss it before any action is taken. 85 | The identity of the reporter will be omitted from the details of the report 86 | supplied to the accused. In potentially harmful situations, such as ongoing 87 | harassment or threats to anyone's safety, we may take action without notice. 88 | 89 | ## Attribution 90 | 91 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 92 | available at 93 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 94 | -------------------------------------------------------------------------------- /docs/reporting-issues.md: -------------------------------------------------------------------------------- 1 | # Data file 2 | 3 | Since all the data is in the browsers local storage only, sending deep link url's is not really a feature. This would only center the map. 4 | For this reason, if you experience an issue, save the data from the dataset via the dropdown save. 5 | Search and replace sensitive strings in this file, for instance vehicle or trip ids. 6 | Compress your cloud logging JSON file before sending this file in a [bug report](https://github.com/googlemaps/fleet-debugger/issues/new/choose). 7 | -------------------------------------------------------------------------------- /docs/screenshots/Fleet_Engine_Logs_Loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/docs/screenshots/Fleet_Engine_Logs_Loading.png -------------------------------------------------------------------------------- /docs/screenshots/dwelltime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/docs/screenshots/dwelltime.png -------------------------------------------------------------------------------- /docs/screenshots/fleetdebugger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/docs/screenshots/fleetdebugger.png -------------------------------------------------------------------------------- /docs/screenshots/gps_accuracy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/docs/screenshots/gps_accuracy.png -------------------------------------------------------------------------------- /docs/screenshots/heading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/docs/screenshots/heading.png -------------------------------------------------------------------------------- /docs/screenshots/planned_paths.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/docs/screenshots/planned_paths.png -------------------------------------------------------------------------------- /docs/screenshots/speed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/docs/screenshots/speed.png -------------------------------------------------------------------------------- /docs/screenshots/tasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/docs/screenshots/tasks.png -------------------------------------------------------------------------------- /docs/screenshots/vehiclereplay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/docs/screenshots/vehiclereplay.gif -------------------------------------------------------------------------------- /dune-buggy.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * Copyright 2021 Google LLC 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | const process = require("process"); 18 | const auth = require("./components/auth.js"); 19 | const logging = require("./components/logging.js"); 20 | const { CloudLogs } = require("./components/cloudLoggingDS.js"); 21 | const { Serve } = require("./components/serve.js"); 22 | const { Bigquery } = require("./components/bigqueryDS.js"); 23 | const { FleetArchiveLogs } = require("./components/fleetArchiveDS.js"); 24 | const _ = require("lodash"); 25 | 26 | const commands = {}; 27 | const yargs = require("yargs/yargs")(process.argv.slice(2)) 28 | .command("live", "view live vehicle movement", () => { 29 | commands.live = true; 30 | }) 31 | .command("historical", "view history vehicle movement", () => { 32 | commands.historical = true; 33 | }) 34 | .command("serve", "start dune-buggy in server mode", () => { 35 | commands.serve = true; 36 | }) 37 | .options({ 38 | vehicle: { 39 | describe: "vehicle id to debug", 40 | }, 41 | trip: { 42 | describe: "trip id to debug", 43 | }, 44 | task: { 45 | describe: "task id to debug", 46 | }, 47 | port: { 48 | describe: "port to use when serve mode enabled", 49 | }, 50 | startTime: { 51 | describe: 52 | "startTime -- Only request data newer that startTime .. Must be an something that used in new Date()", 53 | default: new Date(0).toISOString(), 54 | }, 55 | endTime: { 56 | describe: 57 | "endTime -- Only request data older that endTime .. Must be an something that used in new Date()", 58 | default: new Date().toISOString(), 59 | }, 60 | daysAgo: { 61 | describe: 62 | "Cloud logging is very slow when scanning for old logs, limit search to at most this many days ago", 63 | default: 2, 64 | }, 65 | apikey: { 66 | describe: "apikey", 67 | required: true, 68 | }, 69 | mapId: { 70 | describe: "mapId to use in map", 71 | }, 72 | bigquery: { 73 | describe: 74 | "Use specified bigquery dataset as datasource. Assumes current user has access, and standard format. Assumes dataset has separate tables for each api method", 75 | }, 76 | fleetarchive: { 77 | describe: "Use the fleet archive dataset as datasource.", 78 | boolean: true, 79 | }, 80 | }) 81 | .conflicts("vehicle", "trip"); 82 | const argv = yargs.argv; 83 | 84 | async function getLogs(dataSource, params, vehicle, trip) { 85 | // TOOD: handle lookup by task -- task logs are trickier in than 86 | // updateDeliveryVehicleLogs aren't labeled with the task, since there 87 | // are many tasks at any given time(unlike how 88 | // relevant updateVehicleLogs are labeled by a trip_id) 89 | const vehicleAndTripLogs = await dataSource.fetchVehicleAndTripLogs( 90 | vehicle, 91 | trip, 92 | params.jwt 93 | ); 94 | const deliveryVehicleLogs = await dataSource.fetchDeliveryVehicleLogs( 95 | vehicle, 96 | vehicleAndTripLogs, 97 | params.jwt 98 | ); 99 | const taskLogs = await dataSource.fetchTaskLogsForVehicle( 100 | vehicle, 101 | deliveryVehicleLogs, 102 | params.jwt 103 | ); 104 | 105 | params.solutionType = vehicleAndTripLogs.length === 0 ? "LMFS" : "ODRD"; 106 | 107 | params.rawLogs = _(vehicleAndTripLogs) 108 | .concat(deliveryVehicleLogs) 109 | .concat(taskLogs) 110 | .sortBy((x) => new Date(x.timestamp || x.serverTime).getTime()) 111 | .reverse() 112 | .value(); 113 | } 114 | 115 | async function main() { 116 | if (argv.vehicle) { 117 | console.log("Fetching logs for vehicle id", argv.vehicle); 118 | } else if (argv.trip) { 119 | console.log("Fetching logs for trip id", argv.trip); 120 | } else if (argv.task) { 121 | console.log("Not implemented yet: the task id is:", argv.task); 122 | return; 123 | } else if ((argv.startTime || argv.endTime) && !argv.bigquery) { 124 | console.log( 125 | "startTime and endTime only supported on bigquery dataset. Use --daysAgo" 126 | ); 127 | return; 128 | } else if (!commands.serve) { 129 | yargs.showHelp(); 130 | return; 131 | } 132 | // TODO: error if both trip & vehicle being set 133 | 134 | const params = { 135 | APIKEY: argv.apikey, 136 | live: commands.live, 137 | vehicle: argv.vehicle, 138 | mapId: argv.mapId, 139 | }; 140 | 141 | if (commands.historical) { 142 | await auth.init(); 143 | if (argv.fleetarchive) { 144 | params.jwt = await auth.mintJWT(); 145 | } 146 | } else if (commands.live) { 147 | await auth.init(); 148 | params.jwt = await auth.mintJWT(); 149 | } else if (commands.serve) { 150 | await auth.init(); 151 | } else { 152 | yargs.showHelp(); 153 | return; 154 | } 155 | 156 | // Always include project id -- it's used 157 | // by utils/clean-demos.js 158 | params.projectId = auth.getProjectId(); 159 | let logs; 160 | 161 | if (argv.bigquery) { 162 | logs = new Bigquery(argv); 163 | params.logSource = "bigquery"; 164 | } else if (argv.fleetarchive) { 165 | logs = new FleetArchiveLogs(argv); 166 | params.logSource = "fleetarchive"; 167 | } else { 168 | logs = new CloudLogs(argv); 169 | params.logSource = "cloudlogs"; 170 | } 171 | 172 | if (commands.serve) { 173 | Serve(argv.port || 3000, getLogs, logs, params); 174 | } else { 175 | await getLogs(logs, params, argv.vehicle, argv.trip); 176 | 177 | if (params.rawLogs.length === 0) { 178 | console.error("\n\nError:No log entries found\n\n"); 179 | return; 180 | } 181 | const filePath = `public/data.json`; 182 | 183 | logging.writeLogs(filePath, params); 184 | } 185 | } 186 | 187 | main(); 188 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dune-buggy", 3 | "version": "1.16.0", 4 | "description": "", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/googlemaps/fleet-debugger.git" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "Apache 2.0", 13 | "bugs": { 14 | "url": "https://github.com/googlemaps/fleet-debugger/issues" 15 | }, 16 | "homepage": ".", 17 | "dependencies": { 18 | "@google-cloud/bigquery": "^6.0.0", 19 | "@google-cloud/logging": "^11.0.0", 20 | "@googlemaps/react-wrapper": "^1.1.42", 21 | "@react-oauth/google": "^0.12.1", 22 | "@testing-library/jest-dom": "^5.14.1", 23 | "@testing-library/react": "^11.2.7", 24 | "@testing-library/user-event": "^12.8.3", 25 | "@turf/along": "^7.2.0", 26 | "@turf/helpers": "^7.2.0", 27 | "@turf/turf": "^7.2.0", 28 | "axios": "^1.7.4", 29 | "express": "^4.19.2", 30 | "google-maps-react": "^2.0.6", 31 | "googleapis": "^89.0.0", 32 | "idb": "^8.0.0", 33 | "jszip": "^3.10.1", 34 | "lodash": "^4.17.21", 35 | "open": "^8.3.0", 36 | "query-string": "^7.1.0", 37 | "rc-slider": "^9.7.4", 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2", 40 | "react-json-view": "^1.21.3", 41 | "react-scripts": "5.0.1", 42 | "react-table": "^7.7.0", 43 | "react-toastify": "^9.0.3", 44 | "react-virtualized-auto-sizer": "^1.0.24", 45 | "react-window": "^1.8.10", 46 | "s2polyline-ts": "^1.0.0", 47 | "styled-components": "^5.3.1", 48 | "web-vitals": "^1.1.2", 49 | "webpack": "5.94.0", 50 | "yargs": "^17.2.1" 51 | }, 52 | "devDependencies": { 53 | "@babel/core": "^7.25.2", 54 | "@babel/eslint-parser": "^7.25.1", 55 | "@babel/plugin-proposal-class-properties": "^7.18.6", 56 | "@babel/plugin-transform-class-properties": "^7.24.7", 57 | "@babel/preset-react": "^7.24.7", 58 | "@typescript-eslint/eslint-plugin": "^4.29.3", 59 | "@typescript-eslint/parser": "^4.29.3", 60 | "cross-env": "^7.0.3", 61 | "eslint": "^7.32.0", 62 | "eslint-config-prettier": "^8.10.0", 63 | "eslint-config-react-app": "^7.0.1", 64 | "eslint-plugin-jest": "^25.2.2", 65 | "eslint-plugin-prettier": "^4.0.0", 66 | "eslint-plugin-react": "^7.35.0", 67 | "prettier": "^2.8.8", 68 | "typescript": "^4.4.4" 69 | }, 70 | "scripts": { 71 | "start": "react-scripts start", 72 | "build": "react-scripts build && cp utils/view-bundle.sh build/", 73 | "test": "react-scripts test", 74 | "eject": "react-scripts eject", 75 | "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}\"", 76 | "lint": "cross-env NODE_ENV=development eslint src --ext .js,.jsx,.ts,.tsx", 77 | "docs": "echo NOT IMPLEMENTED" 78 | }, 79 | "eslintConfig": { 80 | "extends": [ 81 | "react-app", 82 | "react-app/jest" 83 | ] 84 | }, 85 | "babel": { 86 | "presets": [ 87 | "react-app" 88 | ], 89 | "plugins": [ 90 | "@babel/plugin-transform-class-properties" 91 | ] 92 | }, 93 | "jest": { 94 | "transformIgnorePatterns": [ 95 | "node_modules/(?!(@turf|polyclip-ts|s2polyline-ts|react-toastify)/.*)" 96 | ] 97 | }, 98 | "browserslist": { 99 | "production": [ 100 | ">0.2%", 101 | "not dead", 102 | "not op_mini all" 103 | ], 104 | "development": [ 105 | "last 1 chrome version", 106 | "last 1 firefox version", 107 | "last 1 safari version" 108 | ] 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Fleet Debugger 28 | 29 | 30 | 31 |
32 | 42 | 43 |
44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlemaps/fleet-debugger/c9bcf8935cea05c9313939f09b68bab7c547382a/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Fleet Debugger", 3 | "name": "Fleet Debugger'", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | // src/App.test.js 2 | 3 | //import { render, screen } from "@testing-library/react"; 4 | //import App from "./App"; 5 | 6 | test("renders learn react link", () => { 7 | expect(true); 8 | /* TODO(#38) 9 | render(); 10 | const linkElement = screen.getByText(/learn react/i); 11 | expect(linkElement).toBeInTheDocument(); 12 | */ 13 | }); 14 | -------------------------------------------------------------------------------- /src/CloudLogging.test.js: -------------------------------------------------------------------------------- 1 | // src/CloudLogging.test.js 2 | import { buildQueryFilter } from "./CloudLogging"; 3 | 4 | describe("buildQueryFilter", () => { 5 | test("builds filter with vehicle ID only", () => { 6 | const params = { 7 | projectId: "test-project", 8 | vehicleId: "vehicle1", 9 | tripIds: "", 10 | startTime: "2023-01-01T01:00:00", 11 | endTime: "2023-01-02T02:00:00", 12 | }; 13 | 14 | const filter = buildQueryFilter(params); 15 | 16 | // Check for key components in the filter 17 | expect(filter).toContain('resource.type="fleetengine.googleapis.com/Fleet"'); 18 | expect(filter).toContain('labels.vehicle_id="vehicle1"'); 19 | expect(filter).toContain("2023-01-01T01:00:00"); 20 | expect(filter).toContain("2023-01-02T02:00:00"); 21 | }); 22 | 23 | test("builds filter with trip IDs only", () => { 24 | const params = { 25 | projectId: "test-project", 26 | vehicleId: "", 27 | tripIds: "trip1,trip2", 28 | startTime: "", 29 | endTime: "", 30 | }; 31 | 32 | const filter = buildQueryFilter(params); 33 | 34 | // Should include regex for multiple trip IDs 35 | expect(filter).toContain('labels.trip_id=~"(trip1|trip2)"'); 36 | }); 37 | 38 | test("builds filter with single trip ID", () => { 39 | const params = { 40 | projectId: "test-project", 41 | vehicleId: "", 42 | tripIds: "trip1", 43 | startTime: "", 44 | endTime: "", 45 | }; 46 | 47 | const filter = buildQueryFilter(params); 48 | 49 | // Should use exact match for single trip ID 50 | expect(filter).toContain('labels.trip_id="trip1"'); 51 | }); 52 | 53 | test("builds filter with both vehicle and trip IDs", () => { 54 | const params = { 55 | projectId: "test-project", 56 | vehicleId: "vehicle1", 57 | tripIds: "trip1,trip2", 58 | startTime: "", 59 | endTime: "", 60 | }; 61 | 62 | const filter = buildQueryFilter(params); 63 | 64 | // Should combine vehicle and trip filters with OR 65 | expect(filter).toContain('(labels.vehicle_id="vehicle1" OR labels.trip_id=~"(trip1|trip2)")'); 66 | }); 67 | 68 | test("throws error for missing project ID", () => { 69 | const params = { 70 | projectId: "", 71 | vehicleId: "vehicle1", 72 | }; 73 | 74 | expect(() => buildQueryFilter(params)).toThrow("Project ID is required"); 75 | }); 76 | 77 | test("throws error when both vehicle ID and trip IDs are missing", () => { 78 | const params = { 79 | projectId: "test-project", 80 | vehicleId: "", 81 | tripIds: "", 82 | }; 83 | 84 | expect(() => buildQueryFilter(params)).toThrow("Either Vehicle ID or at least one Trip ID must be specified"); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /src/Dataframe.js: -------------------------------------------------------------------------------- 1 | /* 2 | * src/Dataframe.js 3 | * 4 | * JSON viewer for log entries. Clicking on a property _value_ 5 | * adds it to the log viewer. 6 | * 7 | */ 8 | import ReactJson from "react-json-view"; 9 | 10 | function Dataframe(props) { 11 | return ( 12 | { 16 | if (field.name === "root") { 17 | return false; 18 | } 19 | 20 | if (field.name === "request" && field.namespace[0] === "root") { 21 | return false; 22 | } 23 | 24 | if (field.name === "vehicle" && field.namespace[0] === "root" && field.namespace[1] === "request") { 25 | return false; 26 | } 27 | 28 | // Collapse everything else 29 | return true; 30 | }} 31 | /> 32 | ); 33 | } 34 | 35 | // TODO: Ideas: allow selecting a field and see how it changes along the map 36 | // or the slider (ie view on map / view on slider) 37 | // or 'add slider' that instantiates a slider that has marks 38 | // when that value changes 39 | export default Dataframe; 40 | -------------------------------------------------------------------------------- /src/ExtraDataSource.js: -------------------------------------------------------------------------------- 1 | // src/ExtraDataSource.js - mock implementation 2 | export default { 3 | isAvailable: () => false, 4 | }; 5 | -------------------------------------------------------------------------------- /src/HighVelocityJump.js: -------------------------------------------------------------------------------- 1 | // src/HighVelocityJump.js 2 | 3 | import _ from "lodash"; 4 | const velocityOutlier = 68; // true velocities higher than this unlikely (in Meters/sec aprrox 150 MPH) 5 | import Stats from "./Stats"; 6 | let computedOutlier = 0; 7 | 8 | class HighVelocityJump { 9 | constructor(jumpIdx, prevEntry, curEntry) { 10 | const prevLoc = prevEntry.lastlocation; 11 | const curLoc = curEntry.lastlocation; 12 | const startLoc = new google.maps.LatLng({ 13 | lat: prevLoc.rawlocation.latitude, 14 | lng: prevLoc.rawlocation.longitude, 15 | }); 16 | const endLoc = new google.maps.LatLng({ 17 | lat: curLoc.rawlocation.latitude, 18 | lng: curLoc.rawlocation.longitude, 19 | }); 20 | 21 | const distanceTraveled = window.google.maps.geometry.spherical.computeDistanceBetween(startLoc, endLoc); 22 | const timeSpentMS = curEntry.date - prevEntry.date; 23 | const velocity = distanceTraveled / (timeSpentMS / 1000.0); 24 | 25 | this.entry = curEntry; 26 | this.prevEntry = prevEntry; 27 | this.timeSpentMS = timeSpentMS; 28 | this.distanceTraveled = distanceTraveled; 29 | this.velocity = velocity; 30 | this.startLoc = startLoc; 31 | this.startDate = prevEntry.date; 32 | this.endDate = curEntry.date; 33 | this.endLoc = endLoc; 34 | this.jumpIdx = jumpIdx; 35 | this.isSignificant = this.distanceTraveled > 1; 36 | } 37 | 38 | /* 39 | * Returns data about the jump to show in json viewer 40 | */ 41 | getFeaturedData() { 42 | return { 43 | timeSpentMS: this.timeSpentMS, 44 | distanceTraveled: this.distanceTraveled, 45 | velocity: this.velocity, 46 | velocityMPH: this.velocity * 2.237, 47 | startLoc: this.startLoc.toString(), 48 | startDate: this.prevEntry.date, 49 | endDate: this.entry.date, 50 | endLoc: this.endLoc.toString(), 51 | jumpIdx: this.jumpIdx, 52 | date: this.entry.date, 53 | computedOutlierVelocity: computedOutlier, 54 | }; 55 | } 56 | 57 | /* 58 | * returns blob of data suitable for viewing in 59 | * the log viewer 60 | */ 61 | getLogViewerEntry() { 62 | const featureData = this.getFeaturedData(); 63 | // Add properties necessary for logviewer to 64 | // function 65 | featureData.timestampMS = this.startDate.getTime(); 66 | featureData.formattedDate = this.startDate.toISOString(); 67 | featureData.lastlocation = { 68 | speed: this.velocity, 69 | }; 70 | featureData["@type"] = "Jump"; 71 | return featureData; 72 | } 73 | 74 | /* 75 | * Filters jumps down to instances where the vehicle was 76 | * travelling at an unrealistic speed (either 77 | * greater that 150 MPH, or 100x median velocity). 78 | * 79 | * These numbers were chosen somewhat arbitrarily 80 | * based on a small dataset. 81 | */ 82 | static getSignificantJumps(jumps) { 83 | if (!jumps || jumps.length === 0) { 84 | console.log("No jumps to process"); 85 | return []; 86 | } 87 | 88 | const velocities = jumps.map((jump) => jump.velocity); 89 | const avgVelocity = _.mean(velocities); 90 | const medianVelocity = Stats.median(velocities); 91 | const stdDevVelocity = Math.sqrt(_.mean(velocities.map((v) => Math.pow(v - avgVelocity, 2)))); 92 | 93 | console.log("avgVelocity", avgVelocity); 94 | console.log("medianVelocity", medianVelocity); 95 | console.log("stdDevVelocity", stdDevVelocity); 96 | 97 | // Consider a jump significant if: 98 | // 1. Its velocity is greater than the median + 2 standard deviations 99 | // 2. OR its velocity is greater than velocityOutlier (150 MPH) 100 | // 3. AND the distance traveled is more than 1 meter 101 | const significantThreshold = Math.min(medianVelocity + 2 * stdDevVelocity, velocityOutlier); 102 | 103 | const significantJumps = jumps.filter( 104 | (jump) => 105 | (jump.velocity > significantThreshold || jump.velocity > velocityOutlier) && 106 | jump.distanceTraveled > 1 && 107 | jump.timeSpentMS > 0 // Ensure we're not dividing by zero 108 | ); 109 | 110 | console.log(`Found ${significantJumps.length} significant jumps`); 111 | return significantJumps; 112 | } 113 | } 114 | 115 | export { HighVelocityJump as default }; 116 | -------------------------------------------------------------------------------- /src/MissingUpdate.js: -------------------------------------------------------------------------------- 1 | // src/MissingUpdate.js 2 | 3 | import _ from "lodash"; 4 | const updateOutlier = 60000; // 60 seconds 5 | import Stats from "./Stats"; 6 | import Utils from "./Utils"; 7 | let computedOutlier = 0; 8 | 9 | class MissingUpdate { 10 | constructor(idx, prevEntry, curEntry) { 11 | const interval = curEntry.date - prevEntry.date; 12 | const curLoc = curEntry.lastlocation; 13 | const prevLoc = prevEntry.lastlocation; 14 | const startLoc = new google.maps.LatLng({ 15 | lat: prevLoc.rawlocation.latitude, 16 | lng: prevLoc.rawlocation.longitude, 17 | }); 18 | const endLoc = new google.maps.LatLng({ 19 | lat: curLoc.rawlocation.latitude, 20 | lng: curLoc.rawlocation.longitude, 21 | }); 22 | this.entry = curEntry; 23 | this.prevEntry = prevEntry; 24 | this.interval = interval; 25 | this.startLoc = startLoc; 26 | this.startDate = prevEntry.date; 27 | this.endDate = curEntry.date; 28 | this.endLoc = endLoc; 29 | this.idx = idx; 30 | this.startVehicleState = _.get(curEntry, "response.vehiclestate"); 31 | this.endVehicleState = _.get(prevEntry, "response.vehiclestate"); 32 | this.duration = Utils.formatDuration(this.interval); 33 | } 34 | 35 | /* 36 | * Returns data about the update to show in json viewer 37 | */ 38 | getFeaturedData() { 39 | return { 40 | duration: this.duration, 41 | interval: this.interval, 42 | startDate: this.startDate, 43 | startLoc: this.startLoc.toString(), 44 | endDate: this.endDate, 45 | endLoc: this.endLoc.toString(), 46 | startVehicleState: this.startVehicleState, 47 | endVehicleState: this.endVehicleState, 48 | computedOutlier: Utils.formatDuration(computedOutlier), 49 | }; 50 | } 51 | 52 | /* 53 | * format a vehicle state transitino into something a 54 | * human can easily read. 55 | */ 56 | getStateTransition() { 57 | if (!(this.startVehicleState && this.endVehicleState)) { 58 | // LMFS doesn't really have vehicle states -- what's interesing here? 59 | return ""; 60 | } 61 | const start = this.startVehicleState.replace("VEHICLE_STATE_", ""); 62 | const end = this.endVehicleState.replace("VEHICLE_STATE_", ""); 63 | return start + ">" + end; 64 | } 65 | 66 | /* 67 | * returns blob of data suitable for viewing in 68 | * the log viewer 69 | */ 70 | getLogViewerEntry() { 71 | const featureData = this.getFeaturedData(); 72 | // Add properties necessary for logviewer to 73 | // function 74 | featureData.date = this.startDate; 75 | featureData.timestampMS = this.startDate.getTime(); 76 | featureData.formattedDate = this.startDate.toISOString(); 77 | featureData["@type"] = "Missing Updates"; 78 | featureData.temporal_gap = featureData.duration; 79 | featureData.response = { 80 | state: this.getStateTransition(), 81 | }; 82 | return featureData; 83 | } 84 | 85 | /* 86 | * Filters updates down to instances where now updates 87 | * were received from the vehicle for either 60 seconds 88 | * or 10x the median observed update (our default 89 | * update is every 5 seconds). 90 | * 91 | * These numbers were chosen somewhat arbitrarily 92 | * based on a small dataset. 93 | */ 94 | static getSignificantMissingUpdates(updates) { 95 | if (!updates) { 96 | return []; 97 | } 98 | const intervals = _.map(updates, "interval"); 99 | const avgInterval = _.mean(intervals); 100 | const medianInterval = Stats.median(intervals); 101 | const minInterval = _.min(intervals); 102 | const maxInterval = _.max(intervals); 103 | console.log("avgInterval", avgInterval); 104 | console.log("medianInterval", medianInterval); 105 | console.log("minInterval", minInterval); 106 | console.log("maxInterval", maxInterval); 107 | console.log("updateOutlier", updateOutlier); 108 | computedOutlier = _.min([medianInterval * 10, updateOutlier]); 109 | console.log("computedOutlier", computedOutlier); 110 | return _(updates) 111 | .filter((e) => e.interval >= computedOutlier) 112 | .sortBy("interval") 113 | .value(); 114 | } 115 | } 116 | export { MissingUpdate as default }; 117 | -------------------------------------------------------------------------------- /src/PolylineCreation.js: -------------------------------------------------------------------------------- 1 | // src/PolylineCreation.js 2 | 3 | import { useState } from "react"; 4 | import { decode } from "s2polyline-ts"; 5 | import { log } from "./Utils"; 6 | 7 | function PolylineCreation({ onSubmit, onClose, buttonPosition }) { 8 | const [input, setInput] = useState(""); 9 | const [opacity, setOpacity] = useState(0.7); 10 | const [color, setColor] = useState("#FF0000"); 11 | const [strokeWeight, setStrokeWeight] = useState(6); 12 | 13 | const handleSubmit = (e) => { 14 | e.preventDefault(); 15 | try { 16 | const trimmedInput = input.trim(); 17 | 18 | // Check if input looks like an encoded polyline (single string without spaces) 19 | if (/^[A-Za-z0-9+/=\-_]+$/.test(trimmedInput)) { 20 | log("Attempting to decode S2 polyline:", trimmedInput); 21 | const decodedPoints = decode(trimmedInput); 22 | 23 | if (decodedPoints && decodedPoints.length > 0) { 24 | // Convert S2 points to our expected format 25 | const validWaypoints = decodedPoints.map((point) => ({ 26 | latitude: point.latDegrees(), 27 | longitude: point.lngDegrees(), 28 | })); 29 | 30 | log(`Decoded ${validWaypoints.length} points from S2 polyline`); 31 | onSubmit(validWaypoints, { opacity, color, strokeWeight }); 32 | setInput(""); 33 | return; 34 | } 35 | } 36 | 37 | // Existing JSON parsing logic 38 | const jsonString = trimmedInput.replace(/(\w+):/g, '"$1":').replace(/\s+/g, " "); 39 | 40 | const inputWithBrackets = jsonString.startsWith("[") && jsonString.endsWith("]") ? jsonString : `[${jsonString}]`; 41 | 42 | const waypoints = JSON.parse(inputWithBrackets); 43 | 44 | const validWaypoints = waypoints.filter( 45 | (waypoint) => 46 | typeof waypoint === "object" && 47 | "latitude" in waypoint && 48 | "longitude" in waypoint && 49 | typeof waypoint.latitude === "number" && 50 | typeof waypoint.longitude === "number" 51 | ); 52 | 53 | if (validWaypoints.length === 0) { 54 | throw new Error("No valid waypoints found"); 55 | } 56 | 57 | log(`Parsed ${validWaypoints.length} valid waypoints`); 58 | onSubmit(validWaypoints, { opacity, color, strokeWeight }); 59 | } catch (error) { 60 | log("Invalid input format:", error); 61 | } 62 | setInput(""); 63 | }; 64 | 65 | let placeholder = `Paste waypoints here: 66 | { latitude: 52.5163, longitude: 13.2399 }, 67 | { latitude: 52.5162, longitude: 13.2400 } 68 | 69 | Or paste an encoded S2 polyline string`; 70 | 71 | return ( 72 |
84 |
85 |